@aglyn/plugins-ai 1.0.0-beta.218 → 1.0.0-beta.220

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 (311) hide show
  1. package/README.md +2 -2
  2. package/package.json +18 -18
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js.map +1 -1
  5. package/src/lib/activity/ai-activity-actions.js +8 -1
  6. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  7. package/src/lib/billing/ai-month-meter.d.ts +51 -0
  8. package/src/lib/billing/ai-month-meter.js +89 -0
  9. package/src/lib/billing/ai-month-meter.js.map +1 -0
  10. package/src/lib/billing/ai-overage-close.d.ts +11 -33
  11. package/src/lib/billing/ai-overage-close.js +4 -3
  12. package/src/lib/billing/ai-overage-close.js.map +1 -1
  13. package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
  14. package/src/lib/billing/ai-overage-cutover.js +1 -28
  15. package/src/lib/billing/ai-overage-cutover.js.map +1 -1
  16. package/src/lib/billing/ai-overage-ledger.js +1 -1
  17. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  18. package/src/lib/billing/ai-overage-standing.js +1 -1
  19. package/src/lib/billing/ai-overage-standing.js.map +1 -1
  20. package/src/lib/components/ai-allotment-editor.component.js +1 -1
  21. package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
  22. package/src/lib/components/ai-assist-provider.component.js +5 -4
  23. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  24. package/src/lib/components/ai-automation-zones.d.ts +63 -0
  25. package/src/lib/components/ai-automation-zones.js +31 -0
  26. package/src/lib/components/ai-automation-zones.js.map +1 -0
  27. package/src/lib/components/ai-brief-dialog.component.js +3 -3
  28. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  29. package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
  30. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  31. package/src/lib/components/ai-describe-button.component.d.ts +6 -3
  32. package/src/lib/components/ai-describe-button.component.js +4 -1
  33. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  34. package/src/lib/components/ai-experiment-cards.component.js +1 -1
  35. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  36. package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
  37. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  38. package/src/lib/components/ai-job-plan.component.d.ts +8 -0
  39. package/src/lib/components/ai-job-plan.component.js +21 -4
  40. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  41. package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
  42. package/src/lib/components/ai-permissions-on-host.component.js +12 -3
  43. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  44. package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
  45. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  46. package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
  47. package/src/lib/components/ai-product-import-option.component.js.map +1 -1
  48. package/src/lib/components/ai-product-zones.d.ts +124 -0
  49. package/src/lib/components/ai-product-zones.js +32 -0
  50. package/src/lib/components/ai-product-zones.js.map +1 -0
  51. package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
  52. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  53. package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
  54. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  55. package/src/lib/components/ai-seo-audit-card.component.js +35 -92
  56. package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
  57. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/assist-jobs-drawer.component.js +21 -15
  60. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  61. package/src/lib/components/assist-panel.component.js +2 -2
  62. package/src/lib/components/assist-panel.component.js.map +1 -1
  63. package/src/lib/components/assist-signals-page.component.js +2 -2
  64. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  65. package/src/lib/components/billing-ai-allotments.component.js +1 -1
  66. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  67. package/src/lib/components/billing-assist-overage-card.component.js +1 -1
  68. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  69. package/src/lib/constants.d.ts +5 -0
  70. package/src/lib/constants.js +4 -0
  71. package/src/lib/constants.js.map +1 -1
  72. package/src/lib/declarations.js +2 -0
  73. package/src/lib/declarations.js.map +1 -1
  74. package/src/lib/declarations.server.js +24 -10
  75. package/src/lib/declarations.server.js.map +1 -1
  76. package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
  77. package/src/lib/insights/ai-dataset-catalog.js +57 -0
  78. package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
  79. package/src/lib/insights/ai-figure-readers.d.ts +6 -39
  80. package/src/lib/insights/ai-figure-readers.js +2 -391
  81. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  82. package/src/lib/insights/ai-insight-digest.d.ts +10 -1
  83. package/src/lib/insights/ai-insight-digest.js +16 -4
  84. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  85. package/src/lib/insights/ai-insight-readers.d.ts +1 -1
  86. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  87. package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
  88. package/src/lib/jobs/ai-email-bindings.js +75 -42
  89. package/src/lib/jobs/ai-email-bindings.js.map +1 -1
  90. package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
  91. package/src/lib/jobs/ai-job-campaign-step.js +5 -10
  92. package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
  94. package/src/lib/jobs/ai-job-component-step.js +32 -9
  95. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-draft-ids.js +11 -0
  97. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  98. package/src/lib/jobs/ai-job-drafts.js +1 -1
  99. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  100. package/src/lib/jobs/ai-job-email-step.d.ts +5 -3
  101. package/src/lib/jobs/ai-job-email-step.js +11 -4
  102. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
  104. package/src/lib/jobs/ai-job-experiment-step.js +1 -1
  105. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
  107. package/src/lib/jobs/ai-job-form-step.js +60 -8
  108. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  109. package/src/lib/jobs/ai-job-generation.d.ts +22 -1
  110. package/src/lib/jobs/ai-job-generation.js +63 -3
  111. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  112. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  113. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  114. package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
  115. package/src/lib/jobs/ai-job-layout-step.js +46 -13
  116. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  117. package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
  118. package/src/lib/jobs/ai-job-page-sections.js +7 -4
  119. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  120. package/src/lib/jobs/ai-job-page-step.js +7 -2
  121. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  122. package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
  123. package/src/lib/jobs/ai-job-plan-conformance.js +40 -1
  124. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  125. package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
  126. package/src/lib/jobs/ai-job-plan-creations.js +199 -0
  127. package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
  128. package/src/lib/jobs/ai-job-plan-step.d.ts +11 -2
  129. package/src/lib/jobs/ai-job-plan-step.js +49 -23
  130. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  131. package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
  132. package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
  133. package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
  134. package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
  135. package/src/lib/jobs/ai-job-products-step.js +54 -32
  136. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  137. package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
  138. package/src/lib/jobs/ai-job-seo-step.js +35 -108
  139. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  140. package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
  141. package/src/lib/jobs/ai-job-site-step.js +6 -6
  142. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  143. package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
  144. package/src/lib/jobs/ai-job-template-step.js +73 -15
  145. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  146. package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
  147. package/src/lib/jobs/ai-job-theme-step.js +1 -1
  148. package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
  149. package/src/lib/jobs/ai-job-workflow-step.js +17 -9
  150. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  151. package/src/lib/jobs/ai-jobs.js +1 -1
  152. package/src/lib/jobs/ai-jobs.js.map +1 -1
  153. package/src/lib/jobs/ai-workflow-records.d.ts +20 -11
  154. package/src/lib/jobs/ai-workflow-records.js +80 -43
  155. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  156. package/src/lib/model/ai-allotments.d.ts +27 -6
  157. package/src/lib/model/ai-allotments.js +54 -14
  158. package/src/lib/model/ai-allotments.js.map +1 -1
  159. package/src/lib/model/ai-automation-outline.d.ts +21 -2
  160. package/src/lib/model/ai-automation-outline.js.map +1 -1
  161. package/src/lib/model/ai-build-plan.d.ts +57 -0
  162. package/src/lib/model/ai-build-plan.js +116 -7
  163. package/src/lib/model/ai-build-plan.js.map +1 -1
  164. package/src/lib/model/ai-job-creations.d.ts +55 -0
  165. package/src/lib/model/ai-job-creations.js +85 -0
  166. package/src/lib/model/ai-job-creations.js.map +1 -0
  167. package/src/lib/model/ai-page-job.d.ts +3 -2
  168. package/src/lib/model/ai-page-job.js +4 -3
  169. package/src/lib/model/ai-page-job.js.map +1 -1
  170. package/src/lib/model/ai-seo.d.ts +11 -51
  171. package/src/lib/model/ai-seo.js +0 -17
  172. package/src/lib/model/ai-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-inventory.js +1 -1
  174. package/src/lib/model/ai-site-inventory.js.map +1 -1
  175. package/src/lib/model/ai-site-job.d.ts +4 -2
  176. package/src/lib/model/ai-site-job.js +8 -4
  177. package/src/lib/model/ai-site-job.js.map +1 -1
  178. package/src/lib/model/ai-template-subjects.d.ts +10 -0
  179. package/src/lib/model/ai-template-subjects.js +16 -2
  180. package/src/lib/model/ai-template-subjects.js.map +1 -1
  181. package/src/lib/model/ai-theme-proposal.d.ts +1 -1
  182. package/src/lib/model/ai-theme-proposal.js +1 -1
  183. package/src/lib/model/ai-theme-proposal.js.map +1 -1
  184. package/src/lib/model/ai-tokens.js +1 -1
  185. package/src/lib/model/ai-tokens.js.map +1 -1
  186. package/src/lib/model/ai-usage-by-user.js +1 -1
  187. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  188. package/src/lib/model/ai-workflow-job.d.ts +1 -3
  189. package/src/lib/model/ai-workflow-job.js +1 -2
  190. package/src/lib/model/ai-workflow-job.js.map +1 -1
  191. package/src/lib/plan-entitlements.d.ts +149 -0
  192. package/src/lib/plan-entitlements.js +168 -0
  193. package/src/lib/plan-entitlements.js.map +1 -0
  194. package/src/lib/plugin.js +1 -1
  195. package/src/lib/plugin.js.map +1 -1
  196. package/src/lib/providers/catalog.js +1 -1
  197. package/src/lib/providers/catalog.js.map +1 -1
  198. package/src/lib/providers/model-choice.js +1 -1
  199. package/src/lib/providers/model-choice.js.map +1 -1
  200. package/src/lib/runtime/ai-device-audit.d.ts +1 -1
  201. package/src/lib/runtime/ai-device-audit.js +1 -1
  202. package/src/lib/runtime/ai-device-audit.js.map +1 -1
  203. package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
  204. package/src/lib/runtime/ai-doctrine-validators.js +178 -21
  205. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  206. package/src/lib/runtime/ai-doctrine.js +5 -5
  207. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  208. package/src/lib/runtime/ai-eval-live.js +30 -15
  209. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  210. package/src/lib/runtime/ai-eval.d.ts +9 -2
  211. package/src/lib/runtime/ai-eval.js +29 -2
  212. package/src/lib/runtime/ai-eval.js.map +1 -1
  213. package/src/lib/runtime/ai-gate.js +1 -1
  214. package/src/lib/runtime/ai-gate.js.map +1 -1
  215. package/src/lib/runtime/ai-node-tree.js +3 -3
  216. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  217. package/src/lib/runtime/ai-palette.d.ts +7 -1
  218. package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
  219. package/src/lib/runtime/ai-palette.generated.js +16 -13
  220. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  221. package/src/lib/runtime/ai-palette.js +7 -1
  222. package/src/lib/runtime/ai-palette.js.map +1 -1
  223. package/src/lib/runtime/seo-audit.d.ts +13 -63
  224. package/src/lib/runtime/seo-audit.js +22 -207
  225. package/src/lib/runtime/seo-audit.js.map +1 -1
  226. package/src/lib/runtime/site-inventory.js +29 -23
  227. package/src/lib/runtime/site-inventory.js.map +1 -1
  228. package/src/lib/server/ai-admin-org.js +1 -1
  229. package/src/lib/server/ai-admin-org.js.map +1 -1
  230. package/src/lib/server/ai-admin-orgs-spend.js +1 -1
  231. package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
  232. package/src/lib/server/ai-admin-signals.js +1 -1
  233. package/src/lib/server/ai-admin-signals.js.map +1 -1
  234. package/src/lib/server/ai-allotments.js +1 -1
  235. package/src/lib/server/ai-allotments.js.map +1 -1
  236. package/src/lib/server/ai-assist-prompts.js +3 -3
  237. package/src/lib/server/ai-assist-prompts.js.map +1 -1
  238. package/src/lib/server/ai-assist.js +2 -2
  239. package/src/lib/server/ai-assist.js.map +1 -1
  240. package/src/lib/server/assist-chat.d.ts +1 -1
  241. package/src/lib/server/assist-chat.js +13 -12
  242. package/src/lib/server/assist-chat.js.map +1 -1
  243. package/src/lib/server/assist-view-context.js +23 -23
  244. package/src/lib/server/assist-view-context.js.map +1 -1
  245. package/src/lib/server/billing-credits.d.ts +5 -0
  246. package/src/lib/server/billing-credits.js +8 -2
  247. package/src/lib/server/billing-credits.js.map +1 -1
  248. package/src/lib/server/billing-overage.js +2 -2
  249. package/src/lib/server/billing-overage.js.map +1 -1
  250. package/src/lib/server.d.ts +1 -0
  251. package/src/lib/server.js.map +1 -1
  252. package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
  253. package/src/lib/testing/stand-in-automation-index.js +102 -0
  254. package/src/lib/testing/stand-in-automation-index.js.map +1 -0
  255. package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
  256. package/src/lib/testing/stand-in-dataset-index.js +80 -0
  257. package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
  258. package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
  259. package/src/lib/testing/stand-in-list-send-time.js +77 -0
  260. package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
  261. package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
  262. package/src/lib/testing/stand-in-pipeline-index.js +59 -0
  263. package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
  264. package/src/lib/testing/stand-in-product-index.d.ts +20 -0
  265. package/src/lib/testing/stand-in-product-index.js +105 -0
  266. package/src/lib/testing/stand-in-product-index.js.map +1 -0
  267. package/src/lib/tools/ai-component-tool.js +2 -2
  268. package/src/lib/tools/ai-component-tool.js.map +1 -1
  269. package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
  270. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  271. package/src/lib/tools/ai-seo-tool.d.ts +6 -18
  272. package/src/lib/tools/ai-seo-tool.js +7 -38
  273. package/src/lib/tools/ai-seo-tool.js.map +1 -1
  274. package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
  275. package/src/lib/tools/ai-workflow-tool.js +1 -1
  276. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  277. package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
  278. package/src/lib/usage/ai-allotment-alerts.js +39 -15
  279. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  280. package/src/lib/usage/ai-quota-checks.d.ts +37 -0
  281. package/src/lib/usage/ai-quota-checks.js +69 -0
  282. package/src/lib/usage/ai-quota-checks.js.map +1 -0
  283. package/src/lib/usage/ai-usage-alerts.js +22 -10
  284. package/src/lib/usage/ai-usage-alerts.js.map +1 -1
  285. package/src/lib/usage/ai-usage-by-user.js +1 -1
  286. package/src/lib/usage/ai-usage-by-user.js.map +1 -1
  287. package/src/lib/usage/assist-ceiling.d.ts +2 -2
  288. package/src/lib/usage/assist-ceiling.js +4 -4
  289. package/src/lib/usage/assist-ceiling.js.map +1 -1
  290. package/src/lib/usage/assist-credits.d.ts +494 -0
  291. package/src/lib/usage/assist-credits.js +524 -0
  292. package/src/lib/usage/assist-credits.js.map +1 -0
  293. package/src/lib/usage/assist-free-taste.d.ts +1 -1
  294. package/src/lib/usage/assist-free-taste.js +3 -2
  295. package/src/lib/usage/assist-free-taste.js.map +1 -1
  296. package/src/lib/usage/assist-signal-mining.js +1 -1
  297. package/src/lib/usage/assist-signal-mining.js.map +1 -1
  298. package/src/lib/usage/assist-spend-guards.d.ts +153 -0
  299. package/src/lib/usage/assist-spend-guards.js +182 -0
  300. package/src/lib/usage/assist-spend-guards.js.map +1 -0
  301. package/src/lib/usage/assist-usage.d.ts +1 -1
  302. package/src/lib/usage/assist-usage.js +1 -1
  303. package/src/lib/usage/assist-usage.js.map +1 -1
  304. package/src/lib/usage/staff-org-ai.js +1 -1
  305. package/src/lib/usage/staff-org-ai.js.map +1 -1
  306. package/src/lib/usage-axes.d.ts +49 -0
  307. package/src/lib/usage-axes.js +108 -0
  308. package/src/lib/usage-axes.js.map +1 -0
  309. package/src/lib/runtime/seo-page-facts.d.ts +0 -61
  310. package/src/lib/runtime/seo-page-facts.js +0 -150
  311. package/src/lib/runtime/seo-page-facts.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/billing/ai-overage-ledger.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/**\n * THE OVERAGE CHARGE LEDGER (AGL-3011).\n *\n * Every dollar of AI overage a workspace is charged mid-period passes\n * through here twice: once as a CLAIM, taken in a transaction before any\n * money is asked for, and once as a SETTLEMENT, recorded after Stripe\n * answers. Nothing in this module talks to Stripe.\n *\n * ## Why claim first\n *\n * The figure that decides a charge — the month's overage — is a counter many\n * requests move. Two requests crossing the threshold a millisecond apart\n * both read \"$25 unbilled\" and would both charge for it. The claim closes\n * that: inside one transaction it moves `overageInvoicedUsd` forward by the\n * amount it is about to charge and records an open charge, so the second\n * request reads an already-claimed balance and stands down. The invoice is\n * created afterwards, outside the transaction, because a Stripe call inside\n * a Firestore transaction would be retried by the transaction.\n *\n * ## What a lost process costs\n *\n * A claim that never became an invoice leaves `overageInvoicedUsd` ahead of\n * what was billed, which UNDER-charges rather than over-charges, and leaves\n * an open charge that blocks the next one. Both are repaired by the\n * reconcile sweep, which asks Stripe whether the claim ever became an\n * invoice and either records it or releases the claim. Under-charging while\n * confused is the direction this must err in.\n *\n * ## The documents\n *\n * `orgs/{orgId}/assistUsage/{YYYY-MM}` — the month's rollup, which the\n * reservation already reads, gains four fields so the gate costs no extra\n * read: `overageInvoicedUsd`, `overagePaidUsd`, `overageCharges` and\n * `overageInvoiceOpen`.\n *\n * `orgs/{orgId}/aiOverageCharges/{chargeId}` — one row per charge, the\n * audit trail staff read and the reconcile sweep walks.\n *\n * Both are server-written: the rules allow the same readers as\n * `aiAllotments` and no writer at all.\n */\n\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { assistMonthOverage } from '@aglyn/aglyn/app-utils/assist-credits'\nimport {\n AI_OVERAGE_MIN_CHARGE_USD,\n AI_OVERAGE_THRESHOLD_USD,\n aiOverageCardOnFile,\n readAiOverageStanding,\n type AiOverageOrg,\n} from './ai-overage-standing'\n\n/** The subcollection and document the standing lives at. */\nexport const AI_BILLING_SUBCOLLECTION = 'aiBilling'\nexport const AI_BILLING_STANDING_DOC = 'standing'\n/** The subcollection one row per charge lives in. */\nexport const AI_OVERAGE_CHARGES_SUBCOLLECTION = 'aiOverageCharges'\n\n/** Which sweep asked for the charge. */\nexport type AiOverageChargeKind = 'threshold' | 'closeout'\n\n/** Where a charge is in its life. */\nexport type AiOverageChargeStatus =\n | 'claimed'\n | 'open'\n | 'paid'\n | 'failed'\n | 'void'\n\n/** A claim taken, ready to be charged. */\nexport interface AiOverageClaim {\n chargeId: string\n orgId: string\n month: string\n seq: number\n kind: AiOverageChargeKind\n /** Floored to the cent — Stripe bills whole cents. */\n amountUsd: number\n /** The credits and rate the amount came from, for the audit row. */\n credits: number\n rateUsdPer1k: number | null\n}\n\n/** Why no claim was taken. Each one is a different thing to do about it. */\nexport type AiOverageClaimRefusal =\n | 'below-threshold'\n | 'charge-already-open'\n | 'no-card'\n | 'paused'\n | 'not-sold'\n\nexport type AiOverageClaimResult =\n | { claimed: AiOverageClaim; refused: null }\n | { claimed: null; refused: AiOverageClaimRefusal }\n\nfunction money(value: unknown): number {\n const number = Number(value)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/** Down to the cent. Never up: a rounded-up cent is a cent never accrued. */\nexport function floorToCent(usd: number): number {\n return Math.floor(money(usd) * 100) / 100\n}\n\n/**\n * `{month}-{seq}` — readable, sortable, and unique per workspace-month.\n *\n * The id is also the Stripe idempotency key's payload and the metadata the\n * reconcile search matches on, so it must survive a URL and a Stripe search\n * query unescaped: digits, a hyphen, nothing else.\n */\nexport function aiOverageChargeId(month: string, seq: number): string {\n return `${month}-${String(Math.max(1, Math.floor(seq))).padStart(3, '0')}`\n}\n\n/**\n * Takes a claim on the workspace's unbilled overage, or explains why not.\n *\n * The four conditions are the design's, in one transaction with the reads\n * they are decided from:\n *\n * - enough unbilled overage (the threshold, or the minimum for a close-out);\n * - no charge already open — one at a time, so a failure has one owner;\n * - a card on file, since the bound assumes a payment method that settles\n * now rather than in days;\n * - not paused.\n *\n * The card condition abstains when the workspace's payment method has never\n * been observed, for the reason `AiOverageStanding.paymentMethodType` gives.\n * A charge attempted against a customer with no default simply fails, and\n * the failure pauses accrual like any other — the guard is an optimization\n * here, not the safety property.\n */\nexport async function claimAiOverageCharge(\n firestore: FirebaseFirestore.Firestore,\n request: {\n orgId: string\n org: AiOverageOrg\n month: string\n kind: AiOverageChargeKind\n now?: Date\n },\n): Promise<AiOverageClaimResult> {\n const now = request.now ?? new Date()\n const orgRef = firestore.collection('orgs').doc(request.orgId)\n const usageRef = orgRef.collection('assistUsage').doc(request.month)\n const standingRef = orgRef\n .collection(AI_BILLING_SUBCOLLECTION)\n .doc(AI_BILLING_STANDING_DOC)\n // A close-out settles a month that is over: there is nothing left to\n // accrue, so anything Stripe will accept is worth billing. A threshold\n // charge waits for the threshold, which is what keeps the invoice count\n // and its fees down.\n const floorUsd =\n request.kind === 'closeout' ? AI_OVERAGE_MIN_CHARGE_USD : AI_OVERAGE_THRESHOLD_USD\n\n return firestore.runTransaction(async (tx) => {\n const [usageSnapshot, standingSnapshot] = await Promise.all([\n tx.get(usageRef),\n tx.get(standingRef),\n ])\n const standing = readAiOverageStanding(\n standingSnapshot.exists ? (standingSnapshot.data() ?? null) : null,\n )\n if (aiOverageCardOnFile(standing) === false) {\n return { claimed: null, refused: 'no-card' as const }\n }\n if (standing.pause) return { claimed: null, refused: 'paused' as const }\n if (usageSnapshot.get('overageInvoiceOpen')) {\n return { claimed: null, refused: 'charge-already-open' as const }\n }\n const priced = assistMonthOverage(\n request.org,\n money(usageSnapshot.get('estCostUsd')),\n )\n // A plan that sells nothing past its band prices its overage to zero, so\n // this is structural rather than a check that could be forgotten — but\n // it is named, because \"we billed nothing\" and \"there is nothing to\n // bill\" are different answers to a staff question.\n if (priced.overageRateUsd === null) {\n return { claimed: null, refused: 'not-sold' as const }\n }\n const invoicedUsd = money(usageSnapshot.get('overageInvoicedUsd'))\n const unbilledUsd = floorToCent(priced.overageMonthlyUsd - invoicedUsd)\n if (!(unbilledUsd >= floorUsd)) {\n return { claimed: null, refused: 'below-threshold' as const }\n }\n const seq = Math.max(0, Math.floor(money(usageSnapshot.get('overageCharges')))) + 1\n const chargeId = aiOverageChargeId(request.month, seq)\n const claim: AiOverageClaim = {\n chargeId,\n orgId: request.orgId,\n month: request.month,\n seq,\n kind: request.kind,\n amountUsd: unbilledUsd,\n credits: priced.overageCredits,\n rateUsdPer1k: priced.overageRateUsd,\n }\n tx.set(\n usageRef,\n {\n month: request.month,\n overageInvoicedUsd: FieldValue.increment(unbilledUsd),\n overageCharges: FieldValue.increment(1),\n overageInvoiceOpen: {\n chargeId,\n invoiceId: null,\n amountUsd: unbilledUsd,\n since: now.toISOString(),\n },\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n tx.set(\n orgRef.collection(AI_OVERAGE_CHARGES_SUBCOLLECTION).doc(chargeId),\n {\n month: request.month,\n seq,\n kind: request.kind,\n amountUsd: unbilledUsd,\n credits: priced.overageCredits,\n rateUsdPer1k: priced.overageRateUsd,\n invoiceId: null,\n status: 'claimed' satisfies AiOverageChargeStatus,\n createdAt: FieldValue.serverTimestamp(),\n paidAt: null,\n failedAt: null,\n },\n { merge: true },\n )\n return { claimed: claim, refused: null }\n })\n}\n\n/** What Stripe said, as the ledger records it. */\nexport interface AiOverageSettlement {\n chargeId: string\n month: string\n /** `null` only when no invoice was ever created. */\n invoiceId: string | null\n status: AiOverageChargeStatus\n /**\n * What Stripe actually collected, in dollars (AGL-3023).\n *\n * The month's paid total moves by THIS and never by the claim. The two are\n * normally equal, and the case where they are not is the one that matters:\n * an invoice that finalized at zero or collected less than it billed would\n * otherwise clear a balance nobody paid, and the gate's unpaid bound would\n * be satisfied by our own bookkeeping instead of by money.\n *\n * Omitted on a settlement that is not a payment, where it is not read.\n */\n paidUsd?: number\n}\n\n/**\n * Records what became of a claimed charge.\n *\n * `paid` adds the amount to the month's `overagePaidUsd`, which is what the\n * gate's unpaid bound subtracts — so paying is what lets a workspace keep\n * going, and nothing else does.\n *\n * An invoice that exists but is not paid KEEPS its claim on\n * `overageInvoicedUsd`: it is a real bill, Stripe's retries may still settle\n * it, and re-billing the same dollars on a second invoice would charge the\n * customer twice for one month's usage. Only a claim that never became an\n * invoice at all releases what it claimed, so those dollars can be charged\n * by a later attempt.\n *\n * The open-charge slot is always cleared. Leaving it set on a failure would\n * block the close-out and every later charge behind an invoice that is\n * never going to move on its own.\n */\nexport async function settleAiOverageCharge(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n settlement: AiOverageSettlement,\n): Promise<void> {\n const orgRef = firestore.collection('orgs').doc(orgId)\n const usageRef = orgRef.collection('assistUsage').doc(settlement.month)\n const chargeRef = orgRef\n .collection(AI_OVERAGE_CHARGES_SUBCOLLECTION)\n .doc(settlement.chargeId)\n\n await firestore.runTransaction(async (tx) => {\n const [usageSnapshot, chargeSnapshot] = await Promise.all([\n tx.get(usageRef),\n tx.get(chargeRef),\n ])\n const amountUsd = money(chargeSnapshot.get('amountUsd'))\n // What the month's paid total moves by: what Stripe took. A settlement\n // that claims to be a payment and names no collected figure moves\n // nothing — silence is not evidence of money (AGL-3023).\n const paidUsd = money(settlement.paidUsd)\n const open = usageSnapshot.get('overageInvoiceOpen') as\n | { chargeId?: unknown }\n | null\n | undefined\n const ownsTheSlot = String(open?.chargeId ?? '') === settlement.chargeId\n // Already settled, or settled by another path (the webhook and the\n // synchronous answer both arrive). Recording the status again is\n // harmless; moving the money counters again is not.\n const alreadyPaid = chargeSnapshot.get('status') === 'paid'\n const releases = settlement.status === 'failed' && !settlement.invoiceId\n tx.set(\n chargeRef,\n {\n invoiceId: settlement.invoiceId,\n status: settlement.status,\n ...(settlement.status === 'paid' ? { paidUsd } : {}),\n ...(settlement.status === 'paid'\n ? { paidAt: FieldValue.serverTimestamp() }\n : {}),\n ...(settlement.status === 'failed'\n ? { failedAt: FieldValue.serverTimestamp() }\n : {}),\n },\n { merge: true },\n )\n tx.set(\n usageRef,\n {\n month: settlement.month,\n ...(settlement.status === 'paid' && !alreadyPaid && paidUsd > 0\n ? { overagePaidUsd: FieldValue.increment(paidUsd) }\n : {}),\n ...(releases\n ? {\n overageInvoicedUsd: FieldValue.increment(-amountUsd),\n overageCharges: FieldValue.increment(-1),\n }\n : {}),\n ...(ownsTheSlot ? { overageInvoiceOpen: null } : {}),\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n })\n}\n\n/** The month's charge counters, as the customer card and staff card read them. */\nexport interface AiOverageMonthLedger {\n invoicedUsd: number\n paidUsd: number\n charges: number\n open: { chargeId: string; invoiceId: string | null; amountUsd: number } | null\n}\n\n/** Reads the four fields off a month's rollup, defensively. */\nexport function readAiOverageMonthLedger(\n data: Record<string, unknown> | null | undefined,\n): AiOverageMonthLedger {\n const open = data?.['overageInvoiceOpen'] as Record<string, unknown> | null | undefined\n const chargeId = typeof open?.['chargeId'] === 'string' ? open['chargeId'] : ''\n return {\n invoicedUsd: money(data?.['overageInvoicedUsd']),\n paidUsd: money(data?.['overagePaidUsd']),\n charges: Math.max(0, Math.floor(money(data?.['overageCharges']))),\n open: chargeId\n ? {\n chargeId,\n invoiceId:\n typeof open?.['invoiceId'] === 'string' ? (open['invoiceId'] as string) : null,\n amountUsd: money(open?.['amountUsd']),\n }\n : null,\n }\n}\n"],"names":["FieldValue","assistMonthOverage","AI_OVERAGE_MIN_CHARGE_USD","AI_OVERAGE_THRESHOLD_USD","aiOverageCardOnFile","readAiOverageStanding","AI_BILLING_SUBCOLLECTION","AI_BILLING_STANDING_DOC","AI_OVERAGE_CHARGES_SUBCOLLECTION","money","value","number","Number","isFinite","floorToCent","usd","Math","floor","aiOverageChargeId","month","seq","String","max","padStart","claimAiOverageCharge","firestore","request","now","Date","orgRef","collection","doc","orgId","usageRef","standingRef","floorUsd","kind","runTransaction","tx","standingSnapshot","usageSnapshot","Promise","all","get","standing","exists","data","claimed","refused","pause","priced","org","overageRateUsd","invoicedUsd","unbilledUsd","overageMonthlyUsd","chargeId","claim","amountUsd","credits","overageCredits","rateUsdPer1k","set","overageInvoicedUsd","increment","overageCharges","overageInvoiceOpen","invoiceId","since","toISOString","updatedAt","serverTimestamp","merge","status","createdAt","paidAt","failedAt","settleAiOverageCharge","settlement","chargeRef","chargeSnapshot","paidUsd","open","ownsTheSlot","alreadyPaid","releases","overagePaidUsd","readAiOverageMonthLedger","charges"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/billing/ai-overage-ledger.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/**\n * THE OVERAGE CHARGE LEDGER (AGL-3011).\n *\n * Every dollar of AI overage a workspace is charged mid-period passes\n * through here twice: once as a CLAIM, taken in a transaction before any\n * money is asked for, and once as a SETTLEMENT, recorded after Stripe\n * answers. Nothing in this module talks to Stripe.\n *\n * ## Why claim first\n *\n * The figure that decides a charge — the month's overage — is a counter many\n * requests move. Two requests crossing the threshold a millisecond apart\n * both read \"$25 unbilled\" and would both charge for it. The claim closes\n * that: inside one transaction it moves `overageInvoicedUsd` forward by the\n * amount it is about to charge and records an open charge, so the second\n * request reads an already-claimed balance and stands down. The invoice is\n * created afterwards, outside the transaction, because a Stripe call inside\n * a Firestore transaction would be retried by the transaction.\n *\n * ## What a lost process costs\n *\n * A claim that never became an invoice leaves `overageInvoicedUsd` ahead of\n * what was billed, which UNDER-charges rather than over-charges, and leaves\n * an open charge that blocks the next one. Both are repaired by the\n * reconcile sweep, which asks Stripe whether the claim ever became an\n * invoice and either records it or releases the claim. Under-charging while\n * confused is the direction this must err in.\n *\n * ## The documents\n *\n * `orgs/{orgId}/assistUsage/{YYYY-MM}` — the month's rollup, which the\n * reservation already reads, gains four fields so the gate costs no extra\n * read: `overageInvoicedUsd`, `overagePaidUsd`, `overageCharges` and\n * `overageInvoiceOpen`.\n *\n * `orgs/{orgId}/aiOverageCharges/{chargeId}` — one row per charge, the\n * audit trail staff read and the reconcile sweep walks.\n *\n * Both are server-written: the rules allow the same readers as\n * `aiAllotments` and no writer at all.\n */\n\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { assistMonthOverage } from '../usage/assist-credits'\nimport {\n AI_OVERAGE_MIN_CHARGE_USD,\n AI_OVERAGE_THRESHOLD_USD,\n aiOverageCardOnFile,\n readAiOverageStanding,\n type AiOverageOrg,\n} from './ai-overage-standing'\n\n/** The subcollection and document the standing lives at. */\nexport const AI_BILLING_SUBCOLLECTION = 'aiBilling'\nexport const AI_BILLING_STANDING_DOC = 'standing'\n/** The subcollection one row per charge lives in. */\nexport const AI_OVERAGE_CHARGES_SUBCOLLECTION = 'aiOverageCharges'\n\n/** Which sweep asked for the charge. */\nexport type AiOverageChargeKind = 'threshold' | 'closeout'\n\n/** Where a charge is in its life. */\nexport type AiOverageChargeStatus =\n | 'claimed'\n | 'open'\n | 'paid'\n | 'failed'\n | 'void'\n\n/** A claim taken, ready to be charged. */\nexport interface AiOverageClaim {\n chargeId: string\n orgId: string\n month: string\n seq: number\n kind: AiOverageChargeKind\n /** Floored to the cent — Stripe bills whole cents. */\n amountUsd: number\n /** The credits and rate the amount came from, for the audit row. */\n credits: number\n rateUsdPer1k: number | null\n}\n\n/** Why no claim was taken. Each one is a different thing to do about it. */\nexport type AiOverageClaimRefusal =\n | 'below-threshold'\n | 'charge-already-open'\n | 'no-card'\n | 'paused'\n | 'not-sold'\n\nexport type AiOverageClaimResult =\n | { claimed: AiOverageClaim; refused: null }\n | { claimed: null; refused: AiOverageClaimRefusal }\n\nfunction money(value: unknown): number {\n const number = Number(value)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/** Down to the cent. Never up: a rounded-up cent is a cent never accrued. */\nexport function floorToCent(usd: number): number {\n return Math.floor(money(usd) * 100) / 100\n}\n\n/**\n * `{month}-{seq}` — readable, sortable, and unique per workspace-month.\n *\n * The id is also the Stripe idempotency key's payload and the metadata the\n * reconcile search matches on, so it must survive a URL and a Stripe search\n * query unescaped: digits, a hyphen, nothing else.\n */\nexport function aiOverageChargeId(month: string, seq: number): string {\n return `${month}-${String(Math.max(1, Math.floor(seq))).padStart(3, '0')}`\n}\n\n/**\n * Takes a claim on the workspace's unbilled overage, or explains why not.\n *\n * The four conditions are the design's, in one transaction with the reads\n * they are decided from:\n *\n * - enough unbilled overage (the threshold, or the minimum for a close-out);\n * - no charge already open — one at a time, so a failure has one owner;\n * - a card on file, since the bound assumes a payment method that settles\n * now rather than in days;\n * - not paused.\n *\n * The card condition abstains when the workspace's payment method has never\n * been observed, for the reason `AiOverageStanding.paymentMethodType` gives.\n * A charge attempted against a customer with no default simply fails, and\n * the failure pauses accrual like any other — the guard is an optimization\n * here, not the safety property.\n */\nexport async function claimAiOverageCharge(\n firestore: FirebaseFirestore.Firestore,\n request: {\n orgId: string\n org: AiOverageOrg\n month: string\n kind: AiOverageChargeKind\n now?: Date\n },\n): Promise<AiOverageClaimResult> {\n const now = request.now ?? new Date()\n const orgRef = firestore.collection('orgs').doc(request.orgId)\n const usageRef = orgRef.collection('assistUsage').doc(request.month)\n const standingRef = orgRef\n .collection(AI_BILLING_SUBCOLLECTION)\n .doc(AI_BILLING_STANDING_DOC)\n // A close-out settles a month that is over: there is nothing left to\n // accrue, so anything Stripe will accept is worth billing. A threshold\n // charge waits for the threshold, which is what keeps the invoice count\n // and its fees down.\n const floorUsd =\n request.kind === 'closeout' ? AI_OVERAGE_MIN_CHARGE_USD : AI_OVERAGE_THRESHOLD_USD\n\n return firestore.runTransaction(async (tx) => {\n const [usageSnapshot, standingSnapshot] = await Promise.all([\n tx.get(usageRef),\n tx.get(standingRef),\n ])\n const standing = readAiOverageStanding(\n standingSnapshot.exists ? (standingSnapshot.data() ?? null) : null,\n )\n if (aiOverageCardOnFile(standing) === false) {\n return { claimed: null, refused: 'no-card' as const }\n }\n if (standing.pause) return { claimed: null, refused: 'paused' as const }\n if (usageSnapshot.get('overageInvoiceOpen')) {\n return { claimed: null, refused: 'charge-already-open' as const }\n }\n const priced = assistMonthOverage(\n request.org,\n money(usageSnapshot.get('estCostUsd')),\n )\n // A plan that sells nothing past its band prices its overage to zero, so\n // this is structural rather than a check that could be forgotten — but\n // it is named, because \"we billed nothing\" and \"there is nothing to\n // bill\" are different answers to a staff question.\n if (priced.overageRateUsd === null) {\n return { claimed: null, refused: 'not-sold' as const }\n }\n const invoicedUsd = money(usageSnapshot.get('overageInvoicedUsd'))\n const unbilledUsd = floorToCent(priced.overageMonthlyUsd - invoicedUsd)\n if (!(unbilledUsd >= floorUsd)) {\n return { claimed: null, refused: 'below-threshold' as const }\n }\n const seq = Math.max(0, Math.floor(money(usageSnapshot.get('overageCharges')))) + 1\n const chargeId = aiOverageChargeId(request.month, seq)\n const claim: AiOverageClaim = {\n chargeId,\n orgId: request.orgId,\n month: request.month,\n seq,\n kind: request.kind,\n amountUsd: unbilledUsd,\n credits: priced.overageCredits,\n rateUsdPer1k: priced.overageRateUsd,\n }\n tx.set(\n usageRef,\n {\n month: request.month,\n overageInvoicedUsd: FieldValue.increment(unbilledUsd),\n overageCharges: FieldValue.increment(1),\n overageInvoiceOpen: {\n chargeId,\n invoiceId: null,\n amountUsd: unbilledUsd,\n since: now.toISOString(),\n },\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n tx.set(\n orgRef.collection(AI_OVERAGE_CHARGES_SUBCOLLECTION).doc(chargeId),\n {\n month: request.month,\n seq,\n kind: request.kind,\n amountUsd: unbilledUsd,\n credits: priced.overageCredits,\n rateUsdPer1k: priced.overageRateUsd,\n invoiceId: null,\n status: 'claimed' satisfies AiOverageChargeStatus,\n createdAt: FieldValue.serverTimestamp(),\n paidAt: null,\n failedAt: null,\n },\n { merge: true },\n )\n return { claimed: claim, refused: null }\n })\n}\n\n/** What Stripe said, as the ledger records it. */\nexport interface AiOverageSettlement {\n chargeId: string\n month: string\n /** `null` only when no invoice was ever created. */\n invoiceId: string | null\n status: AiOverageChargeStatus\n /**\n * What Stripe actually collected, in dollars (AGL-3023).\n *\n * The month's paid total moves by THIS and never by the claim. The two are\n * normally equal, and the case where they are not is the one that matters:\n * an invoice that finalized at zero or collected less than it billed would\n * otherwise clear a balance nobody paid, and the gate's unpaid bound would\n * be satisfied by our own bookkeeping instead of by money.\n *\n * Omitted on a settlement that is not a payment, where it is not read.\n */\n paidUsd?: number\n}\n\n/**\n * Records what became of a claimed charge.\n *\n * `paid` adds the amount to the month's `overagePaidUsd`, which is what the\n * gate's unpaid bound subtracts — so paying is what lets a workspace keep\n * going, and nothing else does.\n *\n * An invoice that exists but is not paid KEEPS its claim on\n * `overageInvoicedUsd`: it is a real bill, Stripe's retries may still settle\n * it, and re-billing the same dollars on a second invoice would charge the\n * customer twice for one month's usage. Only a claim that never became an\n * invoice at all releases what it claimed, so those dollars can be charged\n * by a later attempt.\n *\n * The open-charge slot is always cleared. Leaving it set on a failure would\n * block the close-out and every later charge behind an invoice that is\n * never going to move on its own.\n */\nexport async function settleAiOverageCharge(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n settlement: AiOverageSettlement,\n): Promise<void> {\n const orgRef = firestore.collection('orgs').doc(orgId)\n const usageRef = orgRef.collection('assistUsage').doc(settlement.month)\n const chargeRef = orgRef\n .collection(AI_OVERAGE_CHARGES_SUBCOLLECTION)\n .doc(settlement.chargeId)\n\n await firestore.runTransaction(async (tx) => {\n const [usageSnapshot, chargeSnapshot] = await Promise.all([\n tx.get(usageRef),\n tx.get(chargeRef),\n ])\n const amountUsd = money(chargeSnapshot.get('amountUsd'))\n // What the month's paid total moves by: what Stripe took. A settlement\n // that claims to be a payment and names no collected figure moves\n // nothing — silence is not evidence of money (AGL-3023).\n const paidUsd = money(settlement.paidUsd)\n const open = usageSnapshot.get('overageInvoiceOpen') as\n | { chargeId?: unknown }\n | null\n | undefined\n const ownsTheSlot = String(open?.chargeId ?? '') === settlement.chargeId\n // Already settled, or settled by another path (the webhook and the\n // synchronous answer both arrive). Recording the status again is\n // harmless; moving the money counters again is not.\n const alreadyPaid = chargeSnapshot.get('status') === 'paid'\n const releases = settlement.status === 'failed' && !settlement.invoiceId\n tx.set(\n chargeRef,\n {\n invoiceId: settlement.invoiceId,\n status: settlement.status,\n ...(settlement.status === 'paid' ? { paidUsd } : {}),\n ...(settlement.status === 'paid'\n ? { paidAt: FieldValue.serverTimestamp() }\n : {}),\n ...(settlement.status === 'failed'\n ? { failedAt: FieldValue.serverTimestamp() }\n : {}),\n },\n { merge: true },\n )\n tx.set(\n usageRef,\n {\n month: settlement.month,\n ...(settlement.status === 'paid' && !alreadyPaid && paidUsd > 0\n ? { overagePaidUsd: FieldValue.increment(paidUsd) }\n : {}),\n ...(releases\n ? {\n overageInvoicedUsd: FieldValue.increment(-amountUsd),\n overageCharges: FieldValue.increment(-1),\n }\n : {}),\n ...(ownsTheSlot ? { overageInvoiceOpen: null } : {}),\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n })\n}\n\n/** The month's charge counters, as the customer card and staff card read them. */\nexport interface AiOverageMonthLedger {\n invoicedUsd: number\n paidUsd: number\n charges: number\n open: { chargeId: string; invoiceId: string | null; amountUsd: number } | null\n}\n\n/** Reads the four fields off a month's rollup, defensively. */\nexport function readAiOverageMonthLedger(\n data: Record<string, unknown> | null | undefined,\n): AiOverageMonthLedger {\n const open = data?.['overageInvoiceOpen'] as Record<string, unknown> | null | undefined\n const chargeId = typeof open?.['chargeId'] === 'string' ? open['chargeId'] : ''\n return {\n invoicedUsd: money(data?.['overageInvoicedUsd']),\n paidUsd: money(data?.['overagePaidUsd']),\n charges: Math.max(0, Math.floor(money(data?.['overageCharges']))),\n open: chargeId\n ? {\n chargeId,\n invoiceId:\n typeof open?.['invoiceId'] === 'string' ? (open['invoiceId'] as string) : null,\n amountUsd: money(open?.['amountUsd']),\n }\n : null,\n }\n}\n"],"names":["FieldValue","assistMonthOverage","AI_OVERAGE_MIN_CHARGE_USD","AI_OVERAGE_THRESHOLD_USD","aiOverageCardOnFile","readAiOverageStanding","AI_BILLING_SUBCOLLECTION","AI_BILLING_STANDING_DOC","AI_OVERAGE_CHARGES_SUBCOLLECTION","money","value","number","Number","isFinite","floorToCent","usd","Math","floor","aiOverageChargeId","month","seq","String","max","padStart","claimAiOverageCharge","firestore","request","now","Date","orgRef","collection","doc","orgId","usageRef","standingRef","floorUsd","kind","runTransaction","tx","standingSnapshot","usageSnapshot","Promise","all","get","standing","exists","data","claimed","refused","pause","priced","org","overageRateUsd","invoicedUsd","unbilledUsd","overageMonthlyUsd","chargeId","claim","amountUsd","credits","overageCredits","rateUsdPer1k","set","overageInvoicedUsd","increment","overageCharges","overageInvoiceOpen","invoiceId","since","toISOString","updatedAt","serverTimestamp","merge","status","createdAt","paidAt","failedAt","settleAiOverageCharge","settlement","chargeRef","chargeSnapshot","paidUsd","open","ownsTheSlot","alreadyPaid","releases","overagePaidUsd","readAiOverageMonthLedger","charges"],"mappings":"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"}
@@ -37,7 +37,7 @@
37
37
  * `AI_OVERAGE_THRESHOLD_USD` exists to keep a workspace working, by settling
38
38
  * the balance before it reaches the bound; it is not what makes the bound
39
39
  * true.
40
- */ import { resolveAssistOverageCapUsd } from "@aglyn/aglyn/app-utils/assist-credits";
40
+ */ import { resolveAssistOverageCapUsd } from "../usage/assist-credits.js";
41
41
  /**
42
42
  * The unbilled overage at which the card on file is charged.
43
43
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/billing/ai-overage-standing.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/**\n * A WORKSPACE'S OVERAGE STANDING (AGL-3011) — the pure half.\n *\n * AI credits past a plan's included band are sold on every paid plan. What\n * bounds them is not one number but four facts about the workspace, and this\n * module is the one derivation of all four: whether a card can be charged,\n * whether accrual is paused, how much overage this month may reach, and how\n * much may be outstanding at once.\n *\n * Nothing here reads Firestore, calls Stripe or knows what a month cost.\n * `ai-overage-ledger.ts` holds the documents and `ai-overage-charge.ts` the\n * charging; the gate and the staff card both resolve their figures here, so\n * the ceiling a customer is refused at is the ceiling the card displays.\n *\n * ## The bound, stated once\n *\n * A workspace may leave at most `AI_OVERAGE_UNPAID_LIMIT_USD` of overage\n * unpaid. That is enforced by REFUSING, not by charging — the gate compares\n * what a month accrued against what it paid, inside the reservation\n * transaction, every request. So the bound holds when a charge is late, when\n * Stripe is down, and when the charge path never ran at all. Charging at\n * `AI_OVERAGE_THRESHOLD_USD` exists to keep a workspace working, by settling\n * the balance before it reaches the bound; it is not what makes the bound\n * true.\n */\n\nimport { resolveAssistOverageCapUsd } from '@aglyn/aglyn/app-utils/assist-credits'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\n\n/** The billing fields of an org, as every resolver in this folder takes it. */\nexport type AiOverageOrg = Partial<AglynOrgBilling> | null | undefined\n\n/**\n * The unbilled overage at which the card on file is charged.\n *\n * Fifty times Stripe's minimum charge, so no charge is refused for being too\n * small; low enough that what is outstanding between charges stays near the\n * bound below; high enough that per-invoice fees stay a few percent.\n */\nexport const AI_OVERAGE_THRESHOLD_USD = 25\n\n/**\n * The most overage a workspace may have accrued and not paid for.\n *\n * Twice the threshold, so one charge in flight plus one accruing is inside\n * it, and a workspace doing ordinary work never meets it. Meeting it means\n * the charges are not settling, and that is exactly when accrual must stop.\n */\nexport const AI_OVERAGE_UNPAID_LIMIT_USD = 50\n\n/**\n * The monthly ceiling, by step. It rises one step for each later calendar\n * month in which the workspace paid an invoice with a real charge.\n *\n * Months, not invoices: every paid workspace pays an invoice the moment it\n * subscribes, and threshold invoices can arrive several times a day, so\n * counting invoices would reach the top step in an afternoon. Counting\n * months takes at least two, which covers much of the window in which a\n * stolen card is reported.\n */\nexport const AI_OVERAGE_CEILING_LADDER_USD = [50, 150, 500] as const\n\n/** The top index of the ladder; `step` is clamped to it. */\nexport const AI_OVERAGE_MAX_STEP = AI_OVERAGE_CEILING_LADDER_USD.length - 1\n\n/**\n * The smallest amount worth an invoice — Stripe's own minimum charge. A\n * close-out remainder under it is carried, not billed: an invoice that\n * cannot be paid is a document that dunning chases forever.\n */\nexport const AI_OVERAGE_MIN_CHARGE_USD = 0.5\n\n/** Bounds on a ceiling staff may write by hand, the customer ceiling's. */\nexport const AI_OVERAGE_STAFF_CEILING_MIN_USD = 1\nexport const AI_OVERAGE_STAFF_CEILING_MAX_USD = 100_000\n\n/** Why overage is paused. Each one is payment trust dropping, differently. */\nexport type AiOveragePauseReason =\n | 'charge_failed'\n | 'past_due'\n | 'dispute'\n | 'staff'\n\n/** A staff ceiling that outranks the ladder. */\nexport interface AiOverageStaffOverride {\n ceilingUsd: number\n reason: string\n setBy: string\n setAt: string | null\n /** ISO instant, or `null` to stand until staff clear it. */\n expiresAt: string | null\n}\n\n/** Accrual stopped, and why. */\nexport interface AiOveragePause {\n reason: AiOveragePauseReason\n /** The invoice that failed, when a charge is what stopped it. */\n invoiceId: string | null\n since: string | null\n}\n\n/**\n * `orgs/{orgId}/aiBilling/standing`, normalized.\n *\n * Written only by the server, read by the gate on every request past the\n * band. The Firestore rules deny every client write and allow the same reads\n * as `aiAllotments`.\n */\nexport interface AiOverageStanding {\n /**\n * The workspace's default payment method type, `null` when it has none,\n * and `undefined` when nobody has looked yet.\n *\n * THREE states, not two, and the third is the important one. A workspace\n * that has been paying for a year has no standing document until something\n * writes one, and reading that absence as \"no card\" would refuse a paying\n * customer's work over a document we never wrote. So \"never observed\"\n * declines to answer the card question and lets the other guards bind —\n * the unpaid bound does not rest on the card check, which is why it can\n * afford to abstain. A pre-cutover backfill is what turns `undefined` into\n * a real answer for existing workspaces.\n */\n paymentMethodType: string | null | undefined\n /** The first month (`YYYY-MM`) an invoice of this workspace's was paid. */\n firstPaidMonth: string | null\n /**\n * Later months with a paid invoice, ascending, the most recent 12 kept. A\n * month is dropped again if an AI charge of that month was still unpaid\n * when the month ended.\n */\n qualifyingMonths: string[]\n staffOverride: AiOverageStaffOverride | null\n pause: AiOveragePause | null\n lastDisputeAt: string | null\n}\n\n/** The standing of a workspace nothing has written a document for. */\nexport const EMPTY_AI_OVERAGE_STANDING: AiOverageStanding = {\n paymentMethodType: undefined,\n firstPaidMonth: null,\n qualifyingMonths: [],\n staffOverride: null,\n pause: null,\n lastDisputeAt: null,\n}\n\n/** How many qualifying months the document keeps. */\nexport const AI_OVERAGE_QUALIFYING_MONTHS_KEPT = 12\n\nconst MONTH = /^\\d{4}-\\d{2}$/\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\nfunction readInstant(value: unknown): string | null {\n if (typeof value === 'string' && value.trim()) return value.trim()\n // A Firestore Timestamp, as the Admin SDK hands one back.\n const toDate = (value as { toDate?: () => Date } | null)?.toDate\n if (typeof toDate === 'function') {\n const date = toDate.call(value)\n return date instanceof Date && !Number.isNaN(date.getTime())\n ? date.toISOString()\n : null\n }\n if (value instanceof Date && !Number.isNaN(value.getTime())) {\n return value.toISOString()\n }\n return null\n}\n\nconst PAUSE_REASONS: readonly AiOveragePauseReason[] = [\n 'charge_failed',\n 'past_due',\n 'dispute',\n 'staff',\n]\n\n/**\n * Reads the stored document into the shape above, strictly.\n *\n * Strictly, because every field here decides whether a workspace may spend\n * money: a string `\"true\"` that read as a boolean, or a `NaN` ceiling that\n * every comparison passes, is a guard that is not there. Anything that does\n * not narrow is the empty standing's value for that field — which is the\n * conservative answer in each case except the card, where the deliberate\n * answer is \"nobody has looked\".\n */\nexport function readAiOverageStanding(\n data: Record<string, unknown> | null | undefined,\n): AiOverageStanding {\n if (!data || typeof data !== 'object') return EMPTY_AI_OVERAGE_STANDING\n const rawType = data['paymentMethodType']\n const paymentMethodType =\n rawType === undefined\n ? undefined\n : typeof rawType === 'string' && rawType.trim()\n ? rawType.trim()\n : null\n const firstPaidMonth = readString(data['firstPaidMonth'])\n const months = Array.isArray(data['qualifyingMonths'])\n ? (data['qualifyingMonths'] as unknown[])\n .filter((month): month is string => typeof month === 'string' && MONTH.test(month))\n .filter((month, index, all) => all.indexOf(month) === index)\n .sort()\n : []\n const override = data['staffOverride'] as Record<string, unknown> | null | undefined\n const ceilingUsd = Number(override?.['ceilingUsd'])\n const staffOverride: AiOverageStaffOverride | null =\n override && Number.isFinite(ceilingUsd) && ceilingUsd > 0\n ? {\n ceilingUsd,\n reason: readString(override['reason']) ?? '',\n setBy: readString(override['setBy']) ?? '',\n setAt: readInstant(override['setAt']),\n expiresAt: readInstant(override['expiresAt']),\n }\n : null\n const rawPause = data['pause'] as Record<string, unknown> | null | undefined\n const pauseReason = readString(rawPause?.['reason']) as AiOveragePauseReason | null\n const pause: AiOveragePause | null =\n rawPause && pauseReason && PAUSE_REASONS.includes(pauseReason)\n ? {\n reason: pauseReason,\n invoiceId: readString(rawPause['invoiceId']),\n since: readInstant(rawPause['since']),\n }\n : null\n return {\n paymentMethodType,\n firstPaidMonth: firstPaidMonth && MONTH.test(firstPaidMonth) ? firstPaidMonth : null,\n qualifyingMonths: months,\n staffOverride,\n pause,\n lastDisputeAt: readInstant(data['lastDisputeAt']),\n }\n}\n\n/**\n * The workspace's step on the ladder — one per qualifying month, clamped.\n *\n * Derived rather than stored, so a stored `step` cannot disagree with the\n * months it was supposed to have been derived from. The document carries a\n * mirrored copy for readers that want one figure, and this is what writes it.\n */\nexport function aiOverageStep(standing: AiOverageStanding): number {\n return Math.min(AI_OVERAGE_MAX_STEP, standing.qualifyingMonths.length)\n}\n\n/** Is a staff override still standing at this instant? */\nexport function aiOverageOverrideApplies(\n override: AiOverageStaffOverride | null,\n now: Date,\n): boolean {\n if (!override) return false\n if (!override.expiresAt) return true\n const expires = Date.parse(override.expiresAt)\n // An unparseable expiry expires the override rather than extending it: a\n // ceiling that was meant to end and cannot say when must not outlive its\n // reason because a timestamp did not read back.\n if (!Number.isFinite(expires)) return false\n return now.getTime() < expires\n}\n\n/**\n * Aglyn's own ceiling on this month's overage, in retail dollars.\n *\n * The staff override when one stands, and the ladder otherwise. This is the\n * platform's limit; the customer's own `capUsd` is a separate figure and the\n * lower of the two binds — see `aiOverageEffectiveCeilingUsd`.\n */\nexport function aiOverageStandingCeilingUsd(\n standing: AiOverageStanding,\n now: Date,\n): number {\n if (aiOverageOverrideApplies(standing.staffOverride, now)) {\n return standing.staffOverride!.ceilingUsd\n }\n return AI_OVERAGE_CEILING_LADDER_USD[aiOverageStep(standing)]\n}\n\n/**\n * The ceiling that actually binds: the lower of the workspace's own and\n * Aglyn's. For display and for staff; the gate consults the two separately,\n * because the customer's ceiling and the platform's need different words.\n */\nexport function aiOverageEffectiveCeilingUsd(\n org: AiOverageOrg,\n standing: AiOverageStanding,\n now: Date,\n): number {\n const own = resolveAssistOverageCapUsd(org)\n const ours = aiOverageStandingCeilingUsd(standing, now)\n return own === null ? ours : Math.min(own, ours)\n}\n\n/**\n * Whether a card is the default payment method, as far as anyone has looked.\n *\n * `undefined` when nobody has, and callers must handle that third answer\n * rather than coercing it — see `paymentMethodType`.\n */\nexport function aiOverageCardOnFile(\n standing: AiOverageStanding,\n): boolean | undefined {\n if (standing.paymentMethodType === undefined) return undefined\n return standing.paymentMethodType === 'card'\n}\n\n/**\n * The month a qualifying step is recorded under, from a paid invoice.\n *\n * `null` when the payment is not evidence that money moved or that time has\n * passed: an invoice marked paid out of band is a staff action, a zero\n * payment is a credit, and the first paid month is the baseline the ladder\n * counts FROM rather than a step on it.\n */\nexport function aiOverageQualifyingMonth(\n standing: AiOverageStanding,\n payment: { month: string; amountPaidCents: number; paidOutOfBand: boolean },\n): string | null {\n if (!MONTH.test(payment.month)) return null\n if (payment.paidOutOfBand) return null\n if (!(payment.amountPaidCents > 0)) return null\n if (!standing.firstPaidMonth) return null\n if (!(payment.month > standing.firstPaidMonth)) return null\n return payment.month\n}\n"],"names":["resolveAssistOverageCapUsd","AI_OVERAGE_THRESHOLD_USD","AI_OVERAGE_UNPAID_LIMIT_USD","AI_OVERAGE_CEILING_LADDER_USD","AI_OVERAGE_MAX_STEP","length","AI_OVERAGE_MIN_CHARGE_USD","AI_OVERAGE_STAFF_CEILING_MIN_USD","AI_OVERAGE_STAFF_CEILING_MAX_USD","EMPTY_AI_OVERAGE_STANDING","paymentMethodType","undefined","firstPaidMonth","qualifyingMonths","staffOverride","pause","lastDisputeAt","AI_OVERAGE_QUALIFYING_MONTHS_KEPT","MONTH","readString","value","trim","readInstant","toDate","date","call","Date","Number","isNaN","getTime","toISOString","PAUSE_REASONS","readAiOverageStanding","data","rawType","months","Array","isArray","filter","month","test","index","all","indexOf","sort","override","ceilingUsd","isFinite","reason","setBy","setAt","expiresAt","rawPause","pauseReason","includes","invoiceId","since","aiOverageStep","standing","Math","min","aiOverageOverrideApplies","now","expires","parse","aiOverageStandingCeilingUsd","aiOverageEffectiveCeilingUsd","org","own","ours","aiOverageCardOnFile","aiOverageQualifyingMonth","payment","paidOutOfBand","amountPaidCents"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GAED,SAASA,0BAA0B,QAAQ,wCAAuC;AAMlF;;;;;;CAMC,GACD,OAAO,MAAMC,2BAA2B,GAAE;AAE1C;;;;;;CAMC,GACD,OAAO,MAAMC,8BAA8B,GAAE;AAE7C;;;;;;;;;CASC,GACD,OAAO,MAAMC,gCAAgC;IAAC;IAAI;IAAK;CAAI,CAAS;AAEpE,0DAA0D,GAC1D,OAAO,MAAMC,sBAAsBD,8BAA8BE,MAAM,GAAG,EAAC;AAE3E;;;;CAIC,GACD,OAAO,MAAMC,4BAA4B,IAAG;AAE5C,yEAAyE,GACzE,OAAO,MAAMC,mCAAmC,EAAC;AACjD,OAAO,MAAMC,mCAAmC,OAAO;AA8DvD,oEAAoE,GACpE,OAAO,MAAMC,4BAA+C;IAC1DC,mBAAmBC;IACnBC,gBAAgB;IAChBC,kBAAkB,EAAE;IACpBC,eAAe;IACfC,OAAO;IACPC,eAAe;AACjB,EAAC;AAED,mDAAmD,GACnD,OAAO,MAAMC,oCAAoC,GAAE;AAEnD,MAAMC,QAAQ;AAEd,SAASC,WAAWC,KAAc;IAChC,OAAO,OAAOA,UAAU,YAAYA,MAAMC,IAAI,KAAKD,MAAMC,IAAI,KAAK;AACpE;AAEA,SAASC,YAAYF,KAAc;IACjC,IAAI,OAAOA,UAAU,YAAYA,MAAMC,IAAI,IAAI,OAAOD,MAAMC,IAAI;IAChE,0DAA0D;IAC1D,MAAME,SAAUH,yBAAD,AAACA,MAA0CG,MAAM;IAChE,IAAI,OAAOA,WAAW,YAAY;QAChC,MAAMC,OAAOD,OAAOE,IAAI,CAACL;QACzB,OAAOI,gBAAgBE,QAAQ,CAACC,OAAOC,KAAK,CAACJ,KAAKK,OAAO,MACrDL,KAAKM,WAAW,KAChB;IACN;IACA,IAAIV,iBAAiBM,QAAQ,CAACC,OAAOC,KAAK,CAACR,MAAMS,OAAO,KAAK;QAC3D,OAAOT,MAAMU,WAAW;IAC1B;IACA,OAAO;AACT;AAEA,MAAMC,gBAAiD;IACrD;IACA;IACA;IACA;CACD;AAED;;;;;;;;;CASC,GACD,OAAO,SAASC,sBACdC,IAAgD;QAuBhCd,aACDA;IAtBf,IAAI,CAACc,QAAQ,OAAOA,SAAS,UAAU,OAAOxB;IAC9C,MAAMyB,UAAUD,IAAI,CAAC,oBAAoB;IACzC,MAAMvB,oBACJwB,YAAYvB,YACRA,YACA,OAAOuB,YAAY,YAAYA,QAAQb,IAAI,KACzCa,QAAQb,IAAI,KACZ;IACR,MAAMT,iBAAiBO,WAAWc,IAAI,CAAC,iBAAiB;IACxD,MAAME,SAASC,MAAMC,OAAO,CAACJ,IAAI,CAAC,mBAAmB,IACjD,AAACA,IAAI,CAAC,mBAAmB,CACtBK,MAAM,CAAC,CAACC,QAA2B,OAAOA,UAAU,YAAYrB,MAAMsB,IAAI,CAACD,QAC3ED,MAAM,CAAC,CAACC,OAAOE,OAAOC,MAAQA,IAAIC,OAAO,CAACJ,WAAWE,OACrDG,IAAI,KACP,EAAE;IACN,MAAMC,WAAWZ,IAAI,CAAC,gBAAgB;IACtC,MAAMa,aAAanB,OAAOkB,4BAAAA,QAAU,CAAC,aAAa;IAClD,MAAM/B,gBACJ+B,YAAYlB,OAAOoB,QAAQ,CAACD,eAAeA,aAAa,IACpD;QACEA;QACAE,MAAM,GAAE7B,cAAAA,WAAW0B,QAAQ,CAAC,SAAS,aAA7B1B,cAAkC;QAC1C8B,KAAK,GAAE9B,eAAAA,WAAW0B,QAAQ,CAAC,QAAQ,aAA5B1B,eAAiC;QACxC+B,OAAO5B,YAAYuB,QAAQ,CAAC,QAAQ;QACpCM,WAAW7B,YAAYuB,QAAQ,CAAC,YAAY;IAC9C,IACA;IACN,MAAMO,WAAWnB,IAAI,CAAC,QAAQ;IAC9B,MAAMoB,cAAclC,WAAWiC,4BAAAA,QAAU,CAAC,SAAS;IACnD,MAAMrC,QACJqC,YAAYC,eAAetB,cAAcuB,QAAQ,CAACD,eAC9C;QACEL,QAAQK;QACRE,WAAWpC,WAAWiC,QAAQ,CAAC,YAAY;QAC3CI,OAAOlC,YAAY8B,QAAQ,CAAC,QAAQ;IACtC,IACA;IACN,OAAO;QACL1C;QACAE,gBAAgBA,kBAAkBM,MAAMsB,IAAI,CAAC5B,kBAAkBA,iBAAiB;QAChFC,kBAAkBsB;QAClBrB;QACAC;QACAC,eAAeM,YAAYW,IAAI,CAAC,gBAAgB;IAClD;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAASwB,cAAcC,QAA2B;IACvD,OAAOC,KAAKC,GAAG,CAACxD,qBAAqBsD,SAAS7C,gBAAgB,CAACR,MAAM;AACvE;AAEA,wDAAwD,GACxD,OAAO,SAASwD,yBACdhB,QAAuC,EACvCiB,GAAS;IAET,IAAI,CAACjB,UAAU,OAAO;IACtB,IAAI,CAACA,SAASM,SAAS,EAAE,OAAO;IAChC,MAAMY,UAAUrC,KAAKsC,KAAK,CAACnB,SAASM,SAAS;IAC7C,yEAAyE;IACzE,yEAAyE;IACzE,gDAAgD;IAChD,IAAI,CAACxB,OAAOoB,QAAQ,CAACgB,UAAU,OAAO;IACtC,OAAOD,IAAIjC,OAAO,KAAKkC;AACzB;AAEA;;;;;;CAMC,GACD,OAAO,SAASE,4BACdP,QAA2B,EAC3BI,GAAS;IAET,IAAID,yBAAyBH,SAAS5C,aAAa,EAAEgD,MAAM;QACzD,OAAOJ,SAAS5C,aAAa,CAAEgC,UAAU;IAC3C;IACA,OAAO3C,6BAA6B,CAACsD,cAAcC,UAAU;AAC/D;AAEA;;;;CAIC,GACD,OAAO,SAASQ,6BACdC,GAAiB,EACjBT,QAA2B,EAC3BI,GAAS;IAET,MAAMM,MAAMpE,2BAA2BmE;IACvC,MAAME,OAAOJ,4BAA4BP,UAAUI;IACnD,OAAOM,QAAQ,OAAOC,OAAOV,KAAKC,GAAG,CAACQ,KAAKC;AAC7C;AAEA;;;;;CAKC,GACD,OAAO,SAASC,oBACdZ,QAA2B;IAE3B,IAAIA,SAAShD,iBAAiB,KAAKC,WAAW,OAAOA;IACrD,OAAO+C,SAAShD,iBAAiB,KAAK;AACxC;AAEA;;;;;;;CAOC,GACD,OAAO,SAAS6D,yBACdb,QAA2B,EAC3Bc,OAA2E;IAE3E,IAAI,CAACtD,MAAMsB,IAAI,CAACgC,QAAQjC,KAAK,GAAG,OAAO;IACvC,IAAIiC,QAAQC,aAAa,EAAE,OAAO;IAClC,IAAI,CAAED,CAAAA,QAAQE,eAAe,GAAG,CAAA,GAAI,OAAO;IAC3C,IAAI,CAAChB,SAAS9C,cAAc,EAAE,OAAO;IACrC,IAAI,CAAE4D,CAAAA,QAAQjC,KAAK,GAAGmB,SAAS9C,cAAc,AAAD,GAAI,OAAO;IACvD,OAAO4D,QAAQjC,KAAK;AACtB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/billing/ai-overage-standing.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/**\n * A WORKSPACE'S OVERAGE STANDING (AGL-3011) — the pure half.\n *\n * AI credits past a plan's included band are sold on every paid plan. What\n * bounds them is not one number but four facts about the workspace, and this\n * module is the one derivation of all four: whether a card can be charged,\n * whether accrual is paused, how much overage this month may reach, and how\n * much may be outstanding at once.\n *\n * Nothing here reads Firestore, calls Stripe or knows what a month cost.\n * `ai-overage-ledger.ts` holds the documents and `ai-overage-charge.ts` the\n * charging; the gate and the staff card both resolve their figures here, so\n * the ceiling a customer is refused at is the ceiling the card displays.\n *\n * ## The bound, stated once\n *\n * A workspace may leave at most `AI_OVERAGE_UNPAID_LIMIT_USD` of overage\n * unpaid. That is enforced by REFUSING, not by charging — the gate compares\n * what a month accrued against what it paid, inside the reservation\n * transaction, every request. So the bound holds when a charge is late, when\n * Stripe is down, and when the charge path never ran at all. Charging at\n * `AI_OVERAGE_THRESHOLD_USD` exists to keep a workspace working, by settling\n * the balance before it reaches the bound; it is not what makes the bound\n * true.\n */\n\nimport { resolveAssistOverageCapUsd } from '../usage/assist-credits'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\n\n/** The billing fields of an org, as every resolver in this folder takes it. */\nexport type AiOverageOrg = Partial<AglynOrgBilling> | null | undefined\n\n/**\n * The unbilled overage at which the card on file is charged.\n *\n * Fifty times Stripe's minimum charge, so no charge is refused for being too\n * small; low enough that what is outstanding between charges stays near the\n * bound below; high enough that per-invoice fees stay a few percent.\n */\nexport const AI_OVERAGE_THRESHOLD_USD = 25\n\n/**\n * The most overage a workspace may have accrued and not paid for.\n *\n * Twice the threshold, so one charge in flight plus one accruing is inside\n * it, and a workspace doing ordinary work never meets it. Meeting it means\n * the charges are not settling, and that is exactly when accrual must stop.\n */\nexport const AI_OVERAGE_UNPAID_LIMIT_USD = 50\n\n/**\n * The monthly ceiling, by step. It rises one step for each later calendar\n * month in which the workspace paid an invoice with a real charge.\n *\n * Months, not invoices: every paid workspace pays an invoice the moment it\n * subscribes, and threshold invoices can arrive several times a day, so\n * counting invoices would reach the top step in an afternoon. Counting\n * months takes at least two, which covers much of the window in which a\n * stolen card is reported.\n */\nexport const AI_OVERAGE_CEILING_LADDER_USD = [50, 150, 500] as const\n\n/** The top index of the ladder; `step` is clamped to it. */\nexport const AI_OVERAGE_MAX_STEP = AI_OVERAGE_CEILING_LADDER_USD.length - 1\n\n/**\n * The smallest amount worth an invoice — Stripe's own minimum charge. A\n * close-out remainder under it is carried, not billed: an invoice that\n * cannot be paid is a document that dunning chases forever.\n */\nexport const AI_OVERAGE_MIN_CHARGE_USD = 0.5\n\n/** Bounds on a ceiling staff may write by hand, the customer ceiling's. */\nexport const AI_OVERAGE_STAFF_CEILING_MIN_USD = 1\nexport const AI_OVERAGE_STAFF_CEILING_MAX_USD = 100_000\n\n/** Why overage is paused. Each one is payment trust dropping, differently. */\nexport type AiOveragePauseReason =\n | 'charge_failed'\n | 'past_due'\n | 'dispute'\n | 'staff'\n\n/** A staff ceiling that outranks the ladder. */\nexport interface AiOverageStaffOverride {\n ceilingUsd: number\n reason: string\n setBy: string\n setAt: string | null\n /** ISO instant, or `null` to stand until staff clear it. */\n expiresAt: string | null\n}\n\n/** Accrual stopped, and why. */\nexport interface AiOveragePause {\n reason: AiOveragePauseReason\n /** The invoice that failed, when a charge is what stopped it. */\n invoiceId: string | null\n since: string | null\n}\n\n/**\n * `orgs/{orgId}/aiBilling/standing`, normalized.\n *\n * Written only by the server, read by the gate on every request past the\n * band. The Firestore rules deny every client write and allow the same reads\n * as `aiAllotments`.\n */\nexport interface AiOverageStanding {\n /**\n * The workspace's default payment method type, `null` when it has none,\n * and `undefined` when nobody has looked yet.\n *\n * THREE states, not two, and the third is the important one. A workspace\n * that has been paying for a year has no standing document until something\n * writes one, and reading that absence as \"no card\" would refuse a paying\n * customer's work over a document we never wrote. So \"never observed\"\n * declines to answer the card question and lets the other guards bind —\n * the unpaid bound does not rest on the card check, which is why it can\n * afford to abstain. A pre-cutover backfill is what turns `undefined` into\n * a real answer for existing workspaces.\n */\n paymentMethodType: string | null | undefined\n /** The first month (`YYYY-MM`) an invoice of this workspace's was paid. */\n firstPaidMonth: string | null\n /**\n * Later months with a paid invoice, ascending, the most recent 12 kept. A\n * month is dropped again if an AI charge of that month was still unpaid\n * when the month ended.\n */\n qualifyingMonths: string[]\n staffOverride: AiOverageStaffOverride | null\n pause: AiOveragePause | null\n lastDisputeAt: string | null\n}\n\n/** The standing of a workspace nothing has written a document for. */\nexport const EMPTY_AI_OVERAGE_STANDING: AiOverageStanding = {\n paymentMethodType: undefined,\n firstPaidMonth: null,\n qualifyingMonths: [],\n staffOverride: null,\n pause: null,\n lastDisputeAt: null,\n}\n\n/** How many qualifying months the document keeps. */\nexport const AI_OVERAGE_QUALIFYING_MONTHS_KEPT = 12\n\nconst MONTH = /^\\d{4}-\\d{2}$/\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\nfunction readInstant(value: unknown): string | null {\n if (typeof value === 'string' && value.trim()) return value.trim()\n // A Firestore Timestamp, as the Admin SDK hands one back.\n const toDate = (value as { toDate?: () => Date } | null)?.toDate\n if (typeof toDate === 'function') {\n const date = toDate.call(value)\n return date instanceof Date && !Number.isNaN(date.getTime())\n ? date.toISOString()\n : null\n }\n if (value instanceof Date && !Number.isNaN(value.getTime())) {\n return value.toISOString()\n }\n return null\n}\n\nconst PAUSE_REASONS: readonly AiOveragePauseReason[] = [\n 'charge_failed',\n 'past_due',\n 'dispute',\n 'staff',\n]\n\n/**\n * Reads the stored document into the shape above, strictly.\n *\n * Strictly, because every field here decides whether a workspace may spend\n * money: a string `\"true\"` that read as a boolean, or a `NaN` ceiling that\n * every comparison passes, is a guard that is not there. Anything that does\n * not narrow is the empty standing's value for that field — which is the\n * conservative answer in each case except the card, where the deliberate\n * answer is \"nobody has looked\".\n */\nexport function readAiOverageStanding(\n data: Record<string, unknown> | null | undefined,\n): AiOverageStanding {\n if (!data || typeof data !== 'object') return EMPTY_AI_OVERAGE_STANDING\n const rawType = data['paymentMethodType']\n const paymentMethodType =\n rawType === undefined\n ? undefined\n : typeof rawType === 'string' && rawType.trim()\n ? rawType.trim()\n : null\n const firstPaidMonth = readString(data['firstPaidMonth'])\n const months = Array.isArray(data['qualifyingMonths'])\n ? (data['qualifyingMonths'] as unknown[])\n .filter((month): month is string => typeof month === 'string' && MONTH.test(month))\n .filter((month, index, all) => all.indexOf(month) === index)\n .sort()\n : []\n const override = data['staffOverride'] as Record<string, unknown> | null | undefined\n const ceilingUsd = Number(override?.['ceilingUsd'])\n const staffOverride: AiOverageStaffOverride | null =\n override && Number.isFinite(ceilingUsd) && ceilingUsd > 0\n ? {\n ceilingUsd,\n reason: readString(override['reason']) ?? '',\n setBy: readString(override['setBy']) ?? '',\n setAt: readInstant(override['setAt']),\n expiresAt: readInstant(override['expiresAt']),\n }\n : null\n const rawPause = data['pause'] as Record<string, unknown> | null | undefined\n const pauseReason = readString(rawPause?.['reason']) as AiOveragePauseReason | null\n const pause: AiOveragePause | null =\n rawPause && pauseReason && PAUSE_REASONS.includes(pauseReason)\n ? {\n reason: pauseReason,\n invoiceId: readString(rawPause['invoiceId']),\n since: readInstant(rawPause['since']),\n }\n : null\n return {\n paymentMethodType,\n firstPaidMonth: firstPaidMonth && MONTH.test(firstPaidMonth) ? firstPaidMonth : null,\n qualifyingMonths: months,\n staffOverride,\n pause,\n lastDisputeAt: readInstant(data['lastDisputeAt']),\n }\n}\n\n/**\n * The workspace's step on the ladder — one per qualifying month, clamped.\n *\n * Derived rather than stored, so a stored `step` cannot disagree with the\n * months it was supposed to have been derived from. The document carries a\n * mirrored copy for readers that want one figure, and this is what writes it.\n */\nexport function aiOverageStep(standing: AiOverageStanding): number {\n return Math.min(AI_OVERAGE_MAX_STEP, standing.qualifyingMonths.length)\n}\n\n/** Is a staff override still standing at this instant? */\nexport function aiOverageOverrideApplies(\n override: AiOverageStaffOverride | null,\n now: Date,\n): boolean {\n if (!override) return false\n if (!override.expiresAt) return true\n const expires = Date.parse(override.expiresAt)\n // An unparseable expiry expires the override rather than extending it: a\n // ceiling that was meant to end and cannot say when must not outlive its\n // reason because a timestamp did not read back.\n if (!Number.isFinite(expires)) return false\n return now.getTime() < expires\n}\n\n/**\n * Aglyn's own ceiling on this month's overage, in retail dollars.\n *\n * The staff override when one stands, and the ladder otherwise. This is the\n * platform's limit; the customer's own `capUsd` is a separate figure and the\n * lower of the two binds — see `aiOverageEffectiveCeilingUsd`.\n */\nexport function aiOverageStandingCeilingUsd(\n standing: AiOverageStanding,\n now: Date,\n): number {\n if (aiOverageOverrideApplies(standing.staffOverride, now)) {\n return standing.staffOverride!.ceilingUsd\n }\n return AI_OVERAGE_CEILING_LADDER_USD[aiOverageStep(standing)]\n}\n\n/**\n * The ceiling that actually binds: the lower of the workspace's own and\n * Aglyn's. For display and for staff; the gate consults the two separately,\n * because the customer's ceiling and the platform's need different words.\n */\nexport function aiOverageEffectiveCeilingUsd(\n org: AiOverageOrg,\n standing: AiOverageStanding,\n now: Date,\n): number {\n const own = resolveAssistOverageCapUsd(org)\n const ours = aiOverageStandingCeilingUsd(standing, now)\n return own === null ? ours : Math.min(own, ours)\n}\n\n/**\n * Whether a card is the default payment method, as far as anyone has looked.\n *\n * `undefined` when nobody has, and callers must handle that third answer\n * rather than coercing it — see `paymentMethodType`.\n */\nexport function aiOverageCardOnFile(\n standing: AiOverageStanding,\n): boolean | undefined {\n if (standing.paymentMethodType === undefined) return undefined\n return standing.paymentMethodType === 'card'\n}\n\n/**\n * The month a qualifying step is recorded under, from a paid invoice.\n *\n * `null` when the payment is not evidence that money moved or that time has\n * passed: an invoice marked paid out of band is a staff action, a zero\n * payment is a credit, and the first paid month is the baseline the ladder\n * counts FROM rather than a step on it.\n */\nexport function aiOverageQualifyingMonth(\n standing: AiOverageStanding,\n payment: { month: string; amountPaidCents: number; paidOutOfBand: boolean },\n): string | null {\n if (!MONTH.test(payment.month)) return null\n if (payment.paidOutOfBand) return null\n if (!(payment.amountPaidCents > 0)) return null\n if (!standing.firstPaidMonth) return null\n if (!(payment.month > standing.firstPaidMonth)) return null\n return payment.month\n}\n"],"names":["resolveAssistOverageCapUsd","AI_OVERAGE_THRESHOLD_USD","AI_OVERAGE_UNPAID_LIMIT_USD","AI_OVERAGE_CEILING_LADDER_USD","AI_OVERAGE_MAX_STEP","length","AI_OVERAGE_MIN_CHARGE_USD","AI_OVERAGE_STAFF_CEILING_MIN_USD","AI_OVERAGE_STAFF_CEILING_MAX_USD","EMPTY_AI_OVERAGE_STANDING","paymentMethodType","undefined","firstPaidMonth","qualifyingMonths","staffOverride","pause","lastDisputeAt","AI_OVERAGE_QUALIFYING_MONTHS_KEPT","MONTH","readString","value","trim","readInstant","toDate","date","call","Date","Number","isNaN","getTime","toISOString","PAUSE_REASONS","readAiOverageStanding","data","rawType","months","Array","isArray","filter","month","test","index","all","indexOf","sort","override","ceilingUsd","isFinite","reason","setBy","setAt","expiresAt","rawPause","pauseReason","includes","invoiceId","since","aiOverageStep","standing","Math","min","aiOverageOverrideApplies","now","expires","parse","aiOverageStandingCeilingUsd","aiOverageEffectiveCeilingUsd","org","own","ours","aiOverageCardOnFile","aiOverageQualifyingMonth","payment","paidOutOfBand","amountPaidCents"],"mappings":"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"}
@@ -134,7 +134,7 @@ import { AI_ALLOTMENT_MAX_CREDITS } from "../model/ai-allotments.js";
134
134
  size: "small"
135
135
  }),
136
136
  disabled: busy,
137
- label: "Keep going and alert at 80% and 100% (soft)"
137
+ label: "Keep going and alert at 75%, 80%, 90% and 100% (soft)"
138
138
  })
139
139
  ]
140
140
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-allotment-editor.component.tsx"],"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'use client'\n\nimport {\n Alert,\n Button,\n Checkbox,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n FormHelperText,\n FormLabel,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { AI_ALLOTMENT_MAX_CREDITS, type AiAllotmentMode } from '../model/ai-allotments'\n\nexport interface AiAllotmentValue {\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nexport interface AiAllotmentEditorProps {\n open: boolean\n title: string\n /** One sentence under the title: whose allotment, and what it bounds. */\n description?: string\n /** The allotment as it stands, or `null` for a new one. */\n initial: AiAllotmentValue | null\n /** The models an allowlist may name, from the allotments route. */\n models: ReadonlyArray<{ id: string; label: string }>\n /** The org-wide restriction: models only, no credits and no mode. */\n modelsOnly?: boolean\n /** The workspace's pool, for the note that an allotment never grants past it. */\n poolLimit?: number | null\n busy?: boolean\n error?: string | null\n onClose: () => void\n onSave: (value: AiAllotmentValue) => void\n /** Offered when the allotment exists. */\n onRemove?: () => void\n}\n\n/**\n * THE ALLOTMENT EDITOR (AGL-2942): credits a month, hard or soft, and an\n * optional model allowlist — one dialog for a member, a collaborator, a\n * site, a bulk selection and the org-wide restriction, so the four places a\n * manager sets an allotment ask the same questions in the same words.\n */\nexport function AiAllotmentEditor(props: AiAllotmentEditorProps) {\n const { open, initial, models, modelsOnly, poolLimit, busy, error } = props\n const [credits, setCredits] = useState('')\n const [mode, setMode] = useState<AiAllotmentMode>('hard')\n const [allowed, setAllowed] = useState<string[]>([])\n const [invalid, setInvalid] = useState<string | null>(null)\n\n // Reset from the allotment each time the dialog opens, keyed on the open\n // flag and the values rather than on the object's identity.\n const initialCredits = initial?.credits ?? null\n const initialMode = initial?.mode ?? 'hard'\n const initialModels = (initial?.models ?? []).join('\\n')\n useEffect(() => {\n if (!open) return\n setCredits(initialCredits === null ? '' : String(initialCredits))\n setMode(initialMode)\n setAllowed(initialModels ? initialModels.split('\\n') : [])\n setInvalid(null)\n }, [open, initialCredits, initialMode, initialModels])\n\n const parsedCredits = credits.trim() === '' ? null : Number(credits)\n const save = () => {\n if (!modelsOnly) {\n if (\n parsedCredits !== null &&\n (!Number.isInteger(parsedCredits) || parsedCredits < 1 || parsedCredits > AI_ALLOTMENT_MAX_CREDITS)\n ) {\n setInvalid('Enter a whole number of credits, 1 or more.')\n return\n }\n if (parsedCredits === null && !allowed.length) {\n setInvalid('Set credits, choose models, or remove the allotment.')\n return\n }\n }\n setInvalid(null)\n props.onSave({\n credits: modelsOnly ? null : parsedCredits,\n mode,\n models: allowed.length ? allowed : null,\n })\n }\n const toggle = (id: string) =>\n setAllowed((current) =>\n current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id],\n )\n\n return (\n <Dialog open={open} onClose={busy ? undefined : props.onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{props.title}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>\n {props.description ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.description}\n </Typography>\n ) : null}\n {modelsOnly ? null : (\n <>\n <TextField\n label=\"Credits a month\"\n type=\"number\"\n size=\"small\"\n value={credits}\n onChange={(event) => setCredits(event.target.value)}\n disabled={busy}\n slotProps={{ htmlInput: { min: 1, step: 100 } }}\n helperText={\n poolLimit && parsedCredits && parsedCredits > poolLimit\n ? `Larger than the workspace's ${poolLimit.toLocaleString()} credits — the workspace's pool still stops first.`\n : 'Counted from the first of the month (UTC). Leave empty to limit models only.'\n }\n />\n <div>\n <FormLabel id=\"ai-allotment-mode\">{'At the allotment'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"ai-allotment-mode\"\n value={mode}\n onChange={(event) => setMode(event.target.value as AiAllotmentMode)}\n >\n <FormControlLabel\n value=\"hard\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Stop AI requests (hard)\"\n />\n <FormControlLabel\n value=\"soft\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Keep going and alert at 80% and 100% (soft)\"\n />\n </RadioGroup>\n </div>\n </>\n )}\n {models.length ? (\n <div>\n <FormLabel component=\"legend\">\n {modelsOnly ? 'Models the organization may use' : 'Limit to these models (optional)'}\n </FormLabel>\n <FormGroup>\n {models.map((model) => (\n <FormControlLabel\n key={model.id}\n control={\n <Checkbox\n size=\"small\"\n checked={allowed.includes(model.id)}\n onChange={() => toggle(model.id)}\n disabled={busy}\n />\n }\n label={model.label}\n />\n ))}\n </FormGroup>\n <FormHelperText>\n {modelsOnly\n ? 'None checked means no restriction. Auto keeps choosing among the checked models.'\n : 'None checked means every model the plan offers. Auto keeps choosing among the checked models.'}\n </FormHelperText>\n </div>\n ) : null}\n {invalid || error ? <Alert severity=\"error\">{invalid ?? error}</Alert> : null}\n </DialogContent>\n <DialogActions>\n {props.onRemove ? (\n <Button color=\"error\" disabled={busy} onClick={props.onRemove} sx={{ mr: 'auto' }}>\n {modelsOnly ? 'Clear restriction' : 'Remove allotment'}\n </Button>\n ) : null}\n <Button disabled={busy} onClick={props.onClose}>\n {'Cancel'}\n </Button>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={save}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n >\n {busy ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n </DialogActions>\n </Dialog>\n )\n}\nAiAllotmentEditor.displayName = 'AiAllotmentEditor'\n\nexport default AiAllotmentEditor\n"],"names":["Alert","Button","Checkbox","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","FormHelperText","FormLabel","Radio","RadioGroup","Stack","TextField","Typography","useEffect","useState","AI_ALLOTMENT_MAX_CREDITS","AiAllotmentEditor","props","open","initial","models","modelsOnly","poolLimit","busy","error","credits","setCredits","mode","setMode","allowed","setAllowed","invalid","setInvalid","initialCredits","initialMode","initialModels","join","String","split","parsedCredits","trim","Number","save","isInteger","length","onSave","toggle","id","current","includes","filter","entry","onClose","undefined","maxWidth","fullWidth","title","sx","display","flexDirection","gap","pt","description","variant","color","label","type","size","value","onChange","event","target","disabled","slotProps","htmlInput","min","step","helperText","toLocaleString","div","aria-labelledby","control","component","map","model","checked","severity","onRemove","onClick","mr","direction","spacing","alignItems","startIcon","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-allotment-editor.component.tsx"],"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'use client'\n\nimport {\n Alert,\n Button,\n Checkbox,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n FormHelperText,\n FormLabel,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { AI_ALLOTMENT_MAX_CREDITS, type AiAllotmentMode } from '../model/ai-allotments'\n\nexport interface AiAllotmentValue {\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nexport interface AiAllotmentEditorProps {\n open: boolean\n title: string\n /** One sentence under the title: whose allotment, and what it bounds. */\n description?: string\n /** The allotment as it stands, or `null` for a new one. */\n initial: AiAllotmentValue | null\n /** The models an allowlist may name, from the allotments route. */\n models: ReadonlyArray<{ id: string; label: string }>\n /** The org-wide restriction: models only, no credits and no mode. */\n modelsOnly?: boolean\n /** The workspace's pool, for the note that an allotment never grants past it. */\n poolLimit?: number | null\n busy?: boolean\n error?: string | null\n onClose: () => void\n onSave: (value: AiAllotmentValue) => void\n /** Offered when the allotment exists. */\n onRemove?: () => void\n}\n\n/**\n * THE ALLOTMENT EDITOR (AGL-2942): credits a month, hard or soft, and an\n * optional model allowlist — one dialog for a member, a collaborator, a\n * site, a bulk selection and the org-wide restriction, so the four places a\n * manager sets an allotment ask the same questions in the same words.\n */\nexport function AiAllotmentEditor(props: AiAllotmentEditorProps) {\n const { open, initial, models, modelsOnly, poolLimit, busy, error } = props\n const [credits, setCredits] = useState('')\n const [mode, setMode] = useState<AiAllotmentMode>('hard')\n const [allowed, setAllowed] = useState<string[]>([])\n const [invalid, setInvalid] = useState<string | null>(null)\n\n // Reset from the allotment each time the dialog opens, keyed on the open\n // flag and the values rather than on the object's identity.\n const initialCredits = initial?.credits ?? null\n const initialMode = initial?.mode ?? 'hard'\n const initialModels = (initial?.models ?? []).join('\\n')\n useEffect(() => {\n if (!open) return\n setCredits(initialCredits === null ? '' : String(initialCredits))\n setMode(initialMode)\n setAllowed(initialModels ? initialModels.split('\\n') : [])\n setInvalid(null)\n }, [open, initialCredits, initialMode, initialModels])\n\n const parsedCredits = credits.trim() === '' ? null : Number(credits)\n const save = () => {\n if (!modelsOnly) {\n if (\n parsedCredits !== null &&\n (!Number.isInteger(parsedCredits) || parsedCredits < 1 || parsedCredits > AI_ALLOTMENT_MAX_CREDITS)\n ) {\n setInvalid('Enter a whole number of credits, 1 or more.')\n return\n }\n if (parsedCredits === null && !allowed.length) {\n setInvalid('Set credits, choose models, or remove the allotment.')\n return\n }\n }\n setInvalid(null)\n props.onSave({\n credits: modelsOnly ? null : parsedCredits,\n mode,\n models: allowed.length ? allowed : null,\n })\n }\n const toggle = (id: string) =>\n setAllowed((current) =>\n current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id],\n )\n\n return (\n <Dialog open={open} onClose={busy ? undefined : props.onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{props.title}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>\n {props.description ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.description}\n </Typography>\n ) : null}\n {modelsOnly ? null : (\n <>\n <TextField\n label=\"Credits a month\"\n type=\"number\"\n size=\"small\"\n value={credits}\n onChange={(event) => setCredits(event.target.value)}\n disabled={busy}\n slotProps={{ htmlInput: { min: 1, step: 100 } }}\n helperText={\n poolLimit && parsedCredits && parsedCredits > poolLimit\n ? `Larger than the workspace's ${poolLimit.toLocaleString()} credits — the workspace's pool still stops first.`\n : 'Counted from the first of the month (UTC). Leave empty to limit models only.'\n }\n />\n <div>\n <FormLabel id=\"ai-allotment-mode\">{'At the allotment'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"ai-allotment-mode\"\n value={mode}\n onChange={(event) => setMode(event.target.value as AiAllotmentMode)}\n >\n <FormControlLabel\n value=\"hard\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Stop AI requests (hard)\"\n />\n <FormControlLabel\n value=\"soft\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Keep going and alert at 75%, 80%, 90% and 100% (soft)\"\n />\n </RadioGroup>\n </div>\n </>\n )}\n {models.length ? (\n <div>\n <FormLabel component=\"legend\">\n {modelsOnly ? 'Models the organization may use' : 'Limit to these models (optional)'}\n </FormLabel>\n <FormGroup>\n {models.map((model) => (\n <FormControlLabel\n key={model.id}\n control={\n <Checkbox\n size=\"small\"\n checked={allowed.includes(model.id)}\n onChange={() => toggle(model.id)}\n disabled={busy}\n />\n }\n label={model.label}\n />\n ))}\n </FormGroup>\n <FormHelperText>\n {modelsOnly\n ? 'None checked means no restriction. Auto keeps choosing among the checked models.'\n : 'None checked means every model the plan offers. Auto keeps choosing among the checked models.'}\n </FormHelperText>\n </div>\n ) : null}\n {invalid || error ? <Alert severity=\"error\">{invalid ?? error}</Alert> : null}\n </DialogContent>\n <DialogActions>\n {props.onRemove ? (\n <Button color=\"error\" disabled={busy} onClick={props.onRemove} sx={{ mr: 'auto' }}>\n {modelsOnly ? 'Clear restriction' : 'Remove allotment'}\n </Button>\n ) : null}\n <Button disabled={busy} onClick={props.onClose}>\n {'Cancel'}\n </Button>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={save}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n >\n {busy ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n </DialogActions>\n </Dialog>\n )\n}\nAiAllotmentEditor.displayName = 'AiAllotmentEditor'\n\nexport default AiAllotmentEditor\n"],"names":["Alert","Button","Checkbox","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","FormHelperText","FormLabel","Radio","RadioGroup","Stack","TextField","Typography","useEffect","useState","AI_ALLOTMENT_MAX_CREDITS","AiAllotmentEditor","props","open","initial","models","modelsOnly","poolLimit","busy","error","credits","setCredits","mode","setMode","allowed","setAllowed","invalid","setInvalid","initialCredits","initialMode","initialModels","join","String","split","parsedCredits","trim","Number","save","isInteger","length","onSave","toggle","id","current","includes","filter","entry","onClose","undefined","maxWidth","fullWidth","title","sx","display","flexDirection","gap","pt","description","variant","color","label","type","size","value","onChange","event","target","disabled","slotProps","htmlInput","min","step","helperText","toLocaleString","div","aria-labelledby","control","component","map","model","checked","severity","onRemove","onClick","mr","direction","spacing","alignItems","startIcon","displayName"],"mappings":"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"}
@@ -18,8 +18,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import * as Aglyn from "@aglyn/aglyn";
20
20
  import { checkEntitlement, lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
21
- import { marketplaceDefinitionToNested } from "@aglyn/aglyn/app-utils/node-definition-sanitizer";
21
+ import { portableDefinitionToNested } from "@aglyn/aglyn/app-utils/node-definition-sanitizer";
22
22
  import { AiAssistActionsContext } from "./ai-assist-actions-context.js";
23
+ import { assistNotOnPlanText } from "../usage/assist-credits.js";
23
24
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
24
25
  import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, TextField, Typography } from "@mui/material";
25
26
  import { useCallback, useMemo, useState } from "react";
@@ -121,7 +122,7 @@ const AI_GENERATE_REFUSED = 'Your role does not include AI generation — ask an
121
122
  });
122
123
  }
123
124
  if (!checkEntitlement(org, 'aiAssist')) {
124
- return void enqueueSnackbar('AI assist requires a Pro plan — see Billing to upgrade', {
125
+ return void enqueueSnackbar(assistNotOnPlanText(), {
125
126
  variant: 'warning',
126
127
  persist: false
127
128
  });
@@ -238,7 +239,7 @@ const AI_GENERATE_REFUSED = 'Your role does not include AI generation — ask an
238
239
  });
239
240
  }
240
241
  if (!checkEntitlement(org, 'aiAssist')) {
241
- return void enqueueSnackbar('AI assist requires a Pro plan — see Billing to upgrade', {
242
+ return void enqueueSnackbar(assistNotOnPlanText(), {
242
243
  variant: 'warning',
243
244
  persist: false
244
245
  });
@@ -293,7 +294,7 @@ const AI_GENERATE_REFUSED = 'Your role does not include AI generation — ask an
293
294
  allowDuplicate: true
294
295
  });
295
296
  }
296
- const nested = marketplaceDefinitionToNested(payload.section.rootId, payload.section.nodes);
297
+ const nested = portableDefinitionToNested(payload.section.rootId, payload.section.nodes);
297
298
  if (!nested) {
298
299
  return void enqueueSnackbar('AI returned an empty section', {
299
300
  variant: 'error',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-assist-provider.component.tsx"],"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'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n checkEntitlement,\n lockdownRefusalText,\n parseLockdownRefusal,\n} from '@aglyn/aglyn'\nimport { marketplaceDefinitionToNested } from '@aglyn/aglyn/app-utils/node-definition-sanitizer'\nimport { AiAssistActionsContext } from './ai-assist-actions-context'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { AiModelSelector } from './ai-model-selector.component'\nimport { AiUsageStrip } from './ai-usage-strip.component'\nimport { useAiModelChoice } from './use-ai-model-choice'\nimport { usePublishAiUsageMeter } from './use-ai-usage-meter'\n\n\nexport interface AiAssistProviderProps {\n /** Org billing doc for the entitlement gate (mounted by the shell). */\n org?: unknown\n /**\n * True once `org` is a trustworthy answer (AGL-1380). The shell mounts\n * this provider above the whole console route tree, so it cannot hold its\n * render until the billing doc settles the way the plugin-page route does\n * — and an undefined `org` is not \"unknown\" to `checkEntitlement`, it is\n * the free tier. Without this flag the gates below told a Pro org that AI\n * assist is not on its plan for as long as the read was in flight.\n */\n orgReady?: boolean\n /**\n * The org every assist call is metered against (AGL-2073). The route no\n * longer resolves it from the signed-in user — a user with one paid org was\n * getting assist on all of their free ones, and their spend was landing on\n * whichever org the server happened to resolve.\n */\n orgId?: string\n /**\n * The site the canvas belongs to (AGL-2927), sent with every assist call\n * so the door can decide a site collaborator's permission on THAT site.\n * Null off a host route, where there is no canvas to assist on.\n */\n hostId?: string | null\n /**\n * The reader's AI verdict, resolved by the shell (AGL-2927). The gates\n * below HOLD until `loaded` — a door opened on a pending answer would\n * spend the workspace's credits on a permission nobody has confirmed —\n * and a refused key removes its callback from the context, so the\n * designer draws no control for it. Omitted, the answer is pending.\n */\n aiPermissions?: { loaded: boolean; use: boolean; generate: boolean }\n children?: JSX.Children\n}\n\nconst PERMISSIONS_PENDING = 'Checking your permissions — try again in a moment'\nconst AI_USE_REFUSED =\n 'Your role does not include AI assistance — ask an organization admin'\nconst AI_GENERATE_REFUSED =\n 'Your role does not include AI generation — ask an organization admin'\n\n/**\n * Console-side AI copy assist (AGL-89): provides the designer's \"Rewrite\n * with AI\" callback, hosts the instruction dialog, and calls the assist API\n * (which 501-degrades when no AI provider is configured). The commit\n * spreads the node's current props — `updateNodeProps` REPLACES the props\n * object — and clears any stale rich-text `html` so the new text renders.\n */\nexport function AiAssistProvider(props: AiAssistProviderProps) {\n const org = props.org\n const orgReady = props.orgReady\n const orgId = props.orgId\n const hostId = props.hostId ?? null\n const aiPermissions = props.aiPermissions\n const { children } = props\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const [node, setNode] = useState<Aglyn.NodeSchema<any> | null>(null)\n const [instruction, setInstruction] = useState('')\n // Rewrite target (AGL-130): 'children' for text-editable elements, or\n // any text attribute the element's schema declares (alt text, labels…).\n const [targetProp, setTargetProp] = useState('children')\n const [busy, setBusy] = useState(false)\n // Generate section (AGL-169).\n const [sectionOpen, setSectionOpen] = useState(false)\n const [sectionPrompt, setSectionPrompt] = useState('')\n // The model switch and the usage strip in both dialogs (AGL-2942). Each\n // dialog remembers its own pick; one envelope store serves both strips\n // and the assistant panel's.\n const copyModel = useAiModelChoice({ orgId, hostId, surface: 'copy', kind: 'copy.element' })\n const sectionModel = useAiModelChoice({\n orgId,\n hostId,\n surface: 'section',\n kind: 'copy.section',\n })\n const publishMeter = usePublishAiUsageMeter(orgId)\n\n const textTargets = useMemo(() => {\n const schema = node?.componentSchema\n const textEditable =\n ((schema?.flags?.textEditable ?? Aglyn.FEATURE_FLAG.DISABLED) &\n Aglyn.FEATURE_FLAG.ENABLED) !==\n 0\n const attributes = (schema?.attributes ?? [])\n .filter(\n (field: any) =>\n field.component === Aglyn.FieldComponentType.TEXT_FIELD ||\n field.component === Aglyn.FieldComponentType.TEXTAREA,\n )\n .map((field: any) => ({\n prop: String(field.name),\n label: String(field.label ?? field.name),\n }))\n return textEditable\n ? [{ prop: 'children', label: 'Element text' }, ...attributes]\n : attributes\n }, [node])\n\n const handleRewrite = useCallback(\n (target: Aglyn.NodeSchema<any>) => {\n // The permission first (AGL-2927): it is a fact about the reader, and\n // pending asserts nothing here either. The refusal is unreachable\n // through the designer — a refused key publishes no callback — and\n // stands for any other caller of the context.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.use) {\n return void enqueueSnackbar(AI_USE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: pending asserts nothing. See `orgReady` on the props.\n if (!orgReady) {\n return void enqueueSnackbar(\n 'Checking your plan — try again in a moment',\n { variant: 'info', persist: false },\n )\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(\n 'AI assist requires a Pro plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n }\n setInstruction('')\n setTargetProp('children')\n setNode(target)\n },\n [org, orgReady, aiPermissions, enqueueSnackbar],\n )\n\n // Keep the target valid for the selected element.\n const effectiveTarget = textTargets.some((item) => item.prop === targetProp)\n ? targetProp\n : (textTargets[0]?.prop ?? 'children')\n\n const handleConfirm = useCallback(async () => {\n if (!node || !instruction.trim() || busy) return\n setBusy(true)\n try {\n const current = (Aglyn.canvas.toJSON().nodes as Record<string, any>)[\n node.$id\n ]\n const text =\n typeof current?.props?.[effectiveTarget] === 'string'\n ? (current.props[effectiveTarget] as string)\n : ''\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n text,\n instruction: instruction.trim(),\n ...(copyModel.model ? { model: copyModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // An ai-assist feature lockdown (AGL-1510/1532) reads as a pause, not\n // as \"AI request failed\" — a provider incident or a cost runaway is\n // something WE turned off, and saying so keeps the user's content out\n // of the story.\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.text) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n Aglyn.canvas.updateNodeProps(node, {\n ...current?.props,\n [effectiveTarget]: payload.text,\n // Rich-text elements render `html` over `children`; drop it so the\n // rewrite is what shows (the user can re-format inline afterwards).\n ...(effectiveTarget === 'children' ? { html: undefined } : {}),\n })\n setNode(null)\n enqueueSnackbar('Text rewritten — undo restores the original', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n node,\n instruction,\n busy,\n user,\n effectiveTarget,\n orgId,\n hostId,\n copyModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n const handleGenerateSection = useCallback(() => {\n // The permission first (AGL-2927) — a section is a generation, so this\n // door sells under `ai.generate`; see `handleRewrite`.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.generate) {\n return void enqueueSnackbar(AI_GENERATE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: same gate as `handleRewrite`.\n if (!orgReady) {\n return void enqueueSnackbar('Checking your plan — try again in a moment', {\n variant: 'info',\n persist: false,\n })\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(\n 'AI assist requires a Pro plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n }\n setSectionPrompt('')\n setSectionOpen(true)\n }, [org, orgReady, aiPermissions, enqueueSnackbar])\n\n const handleSectionConfirm = useCallback(async () => {\n if (!sectionPrompt.trim() || busy) return\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n mode: 'section',\n instruction: sectionPrompt.trim(),\n ...(sectionModel.model ? { model: sectionModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // Same pause notice on the section-generation door (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.section?.rootId) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n const nested = marketplaceDefinitionToNested(\n payload.section.rootId,\n payload.section.nodes,\n )\n if (!nested) {\n return void enqueueSnackbar('AI returned an empty section', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n // Graft through the preset path: ids regenerate, history is saved,\n // and the subtree lands at the end of the canvas root.\n const stamped = (function stamp(node: any): any {\n const schema = Aglyn.components.getSchema(node.componentId)\n return {\n ...node,\n ...(schema?.pluginId ? { pluginId: schema.pluginId } : {}),\n nodes: (node.nodes ?? []).map(stamp),\n }\n })(nested)\n Aglyn.canvas.addNodeFromPreset(\n { type: Aglyn.NodeType.PRESET, data: stamped } as any,\n Aglyn.canvas.rootNode as any,\n )\n setSectionOpen(false)\n enqueueSnackbar('Section added — undo removes it', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n sectionPrompt,\n busy,\n user,\n orgId,\n hostId,\n sectionModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n // A REFUSED key publishes no callback (AGL-2927): the besigner controls\n // render only when the callback exists, so the refusal removes\n // the button rather than dimming it. A PENDING answer keeps both\n // callbacks, and each holds with a notice when pressed — a control that\n // vanishes and reappears while the read lands is worse than one that is\n // briefly inert (the `useHostRole` argument, applied here).\n const refusedUse = aiPermissions?.loaded === true && !aiPermissions.use\n const refusedGenerate =\n aiPermissions?.loaded === true && !aiPermissions.generate\n const contextValue = useMemo(\n () => ({\n ...(refusedUse ? {} : { onRewrite: handleRewrite }),\n ...(refusedGenerate ? {} : { onGenerateSection: handleGenerateSection }),\n }),\n [handleRewrite, handleGenerateSection, refusedUse, refusedGenerate],\n )\n\n return (\n <AiAssistActionsContext.Provider value={contextValue}>\n {children}\n <Dialog\n open={Boolean(node)}\n onClose={() => (busy ? null : setNode(null))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Rewrite with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe how the text should change — tone, length, message.'}\n </Typography>\n {textTargets.length > 1 ? (\n <TextField\n select\n label=\"Field\"\n value={effectiveTarget}\n onChange={(event) => setTargetProp(event.target.value)}\n size=\"small\"\n disabled={busy}\n >\n {textTargets.map((item) => (\n <MenuItem key={item.prop} value={item.prop}>\n {item.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n label=\"Instruction\"\n placeholder=\"e.g. Make it punchier and half as long\"\n value={instruction}\n onChange={(event) => setInstruction(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleConfirm()\n }\n }}\n />\n <AiModelSelector choice={copyModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setNode(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!instruction.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleConfirm}\n >\n {busy ? 'Rewriting…' : 'Rewrite'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={sectionOpen}\n onClose={() => (busy ? null : setSectionOpen(false))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Generate a section with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe the section — e.g. \"hero with headline, subtext, ' +\n 'and a call-to-action button\" or \"three-column feature grid\".'}\n </Typography>\n <TextField\n label=\"Section\"\n value={sectionPrompt}\n onChange={(event) => setSectionPrompt(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleSectionConfirm()\n }\n }}\n />\n <AiModelSelector choice={sectionModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setSectionOpen(false)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!sectionPrompt.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleSectionConfirm}\n >\n {busy ? 'Generating…' : 'Generate'}\n </Button>\n </DialogActions>\n </Dialog>\n </AiAssistActionsContext.Provider>\n )\n}\nAiAssistProvider.displayName = 'AiAssistProvider'\n\nexport default AiAssistProvider\n"],"names":["Aglyn","checkEntitlement","lockdownRefusalText","parseLockdownRefusal","marketplaceDefinitionToNested","AiAssistActionsContext","useSnackbar","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","TextField","Typography","useCallback","useMemo","useState","useUser","authorizedFetch","AiModelSelector","AiUsageStrip","useAiModelChoice","usePublishAiUsageMeter","PERMISSIONS_PENDING","AI_USE_REFUSED","AI_GENERATE_REFUSED","AiAssistProvider","props","textTargets","org","orgReady","orgId","hostId","aiPermissions","children","enqueueSnackbar","data","user","node","setNode","instruction","setInstruction","targetProp","setTargetProp","busy","setBusy","sectionOpen","setSectionOpen","sectionPrompt","setSectionPrompt","copyModel","surface","kind","sectionModel","publishMeter","schema","componentSchema","textEditable","flags","FEATURE_FLAG","DISABLED","ENABLED","attributes","filter","field","component","FieldComponentType","TEXT_FIELD","TEXTAREA","map","prop","String","name","label","handleRewrite","target","loaded","variant","persist","use","effectiveTarget","some","item","handleConfirm","trim","current","canvas","toJSON","nodes","$id","text","response","method","headers","body","JSON","stringify","model","payload","json","meter","locked","status","ok","error","allowDuplicate","updateNodeProps","html","undefined","console","handleGenerateSection","generate","handleSectionConfirm","mode","section","rootId","nested","stamped","stamp","components","getSchema","componentId","pluginId","addNodeFromPreset","type","NodeType","PRESET","rootNode","refusedUse","refusedGenerate","contextValue","onRewrite","onGenerateSection","Provider","value","open","Boolean","onClose","maxWidth","fullWidth","sx","display","flexDirection","gap","pt","color","length","select","onChange","event","size","disabled","placeholder","autoFocus","multiline","minRows","onKeyDown","key","metaKey","ctrlKey","preventDefault","choice","onClick","startIcon","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-assist-provider.component.tsx"],"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'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n checkEntitlement,\n lockdownRefusalText,\n parseLockdownRefusal,\n} from '@aglyn/aglyn'\nimport { portableDefinitionToNested } from '@aglyn/aglyn/app-utils/node-definition-sanitizer'\nimport { AiAssistActionsContext } from './ai-assist-actions-context'\nimport { assistNotOnPlanText } from '../usage/assist-credits'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { AiModelSelector } from './ai-model-selector.component'\nimport { AiUsageStrip } from './ai-usage-strip.component'\nimport { useAiModelChoice } from './use-ai-model-choice'\nimport { usePublishAiUsageMeter } from './use-ai-usage-meter'\n\n\nexport interface AiAssistProviderProps {\n /** Org billing doc for the entitlement gate (mounted by the shell). */\n org?: unknown\n /**\n * True once `org` is a trustworthy answer (AGL-1380). The shell mounts\n * this provider above the whole console route tree, so it cannot hold its\n * render until the billing doc settles the way the plugin-page route does\n * — and an undefined `org` is not \"unknown\" to `checkEntitlement`, it is\n * the free tier. Without this flag the gates below told a Pro org that AI\n * assist is not on its plan for as long as the read was in flight.\n */\n orgReady?: boolean\n /**\n * The org every assist call is metered against (AGL-2073). The route no\n * longer resolves it from the signed-in user — a user with one paid org was\n * getting assist on all of their free ones, and their spend was landing on\n * whichever org the server happened to resolve.\n */\n orgId?: string\n /**\n * The site the canvas belongs to (AGL-2927), sent with every assist call\n * so the door can decide a site collaborator's permission on THAT site.\n * Null off a host route, where there is no canvas to assist on.\n */\n hostId?: string | null\n /**\n * The reader's AI verdict, resolved by the shell (AGL-2927). The gates\n * below HOLD until `loaded` — a door opened on a pending answer would\n * spend the workspace's credits on a permission nobody has confirmed —\n * and a refused key removes its callback from the context, so the\n * designer draws no control for it. Omitted, the answer is pending.\n */\n aiPermissions?: { loaded: boolean; use: boolean; generate: boolean }\n children?: JSX.Children\n}\n\nconst PERMISSIONS_PENDING = 'Checking your permissions — try again in a moment'\nconst AI_USE_REFUSED =\n 'Your role does not include AI assistance — ask an organization admin'\nconst AI_GENERATE_REFUSED =\n 'Your role does not include AI generation — ask an organization admin'\n\n/**\n * Console-side AI copy assist (AGL-89): provides the designer's \"Rewrite\n * with AI\" callback, hosts the instruction dialog, and calls the assist API\n * (which 501-degrades when no AI provider is configured). The commit\n * spreads the node's current props — `updateNodeProps` REPLACES the props\n * object — and clears any stale rich-text `html` so the new text renders.\n */\nexport function AiAssistProvider(props: AiAssistProviderProps) {\n const org = props.org\n const orgReady = props.orgReady\n const orgId = props.orgId\n const hostId = props.hostId ?? null\n const aiPermissions = props.aiPermissions\n const { children } = props\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const [node, setNode] = useState<Aglyn.NodeSchema<any> | null>(null)\n const [instruction, setInstruction] = useState('')\n // Rewrite target (AGL-130): 'children' for text-editable elements, or\n // any text attribute the element's schema declares (alt text, labels…).\n const [targetProp, setTargetProp] = useState('children')\n const [busy, setBusy] = useState(false)\n // Generate section (AGL-169).\n const [sectionOpen, setSectionOpen] = useState(false)\n const [sectionPrompt, setSectionPrompt] = useState('')\n // The model switch and the usage strip in both dialogs (AGL-2942). Each\n // dialog remembers its own pick; one envelope store serves both strips\n // and the assistant panel's.\n const copyModel = useAiModelChoice({ orgId, hostId, surface: 'copy', kind: 'copy.element' })\n const sectionModel = useAiModelChoice({\n orgId,\n hostId,\n surface: 'section',\n kind: 'copy.section',\n })\n const publishMeter = usePublishAiUsageMeter(orgId)\n\n const textTargets = useMemo(() => {\n const schema = node?.componentSchema\n const textEditable =\n ((schema?.flags?.textEditable ?? Aglyn.FEATURE_FLAG.DISABLED) &\n Aglyn.FEATURE_FLAG.ENABLED) !==\n 0\n const attributes = (schema?.attributes ?? [])\n .filter(\n (field: any) =>\n field.component === Aglyn.FieldComponentType.TEXT_FIELD ||\n field.component === Aglyn.FieldComponentType.TEXTAREA,\n )\n .map((field: any) => ({\n prop: String(field.name),\n label: String(field.label ?? field.name),\n }))\n return textEditable\n ? [{ prop: 'children', label: 'Element text' }, ...attributes]\n : attributes\n }, [node])\n\n const handleRewrite = useCallback(\n (target: Aglyn.NodeSchema<any>) => {\n // The permission first (AGL-2927): it is a fact about the reader, and\n // pending asserts nothing here either. The refusal is unreachable\n // through the designer — a refused key publishes no callback — and\n // stands for any other caller of the context.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.use) {\n return void enqueueSnackbar(AI_USE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: pending asserts nothing. See `orgReady` on the props.\n if (!orgReady) {\n return void enqueueSnackbar(\n 'Checking your plan — try again in a moment',\n { variant: 'info', persist: false },\n )\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(assistNotOnPlanText(), {\n variant: 'warning',\n persist: false,\n })\n }\n setInstruction('')\n setTargetProp('children')\n setNode(target)\n },\n [org, orgReady, aiPermissions, enqueueSnackbar],\n )\n\n // Keep the target valid for the selected element.\n const effectiveTarget = textTargets.some((item) => item.prop === targetProp)\n ? targetProp\n : (textTargets[0]?.prop ?? 'children')\n\n const handleConfirm = useCallback(async () => {\n if (!node || !instruction.trim() || busy) return\n setBusy(true)\n try {\n const current = (Aglyn.canvas.toJSON().nodes as Record<string, any>)[\n node.$id\n ]\n const text =\n typeof current?.props?.[effectiveTarget] === 'string'\n ? (current.props[effectiveTarget] as string)\n : ''\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n text,\n instruction: instruction.trim(),\n ...(copyModel.model ? { model: copyModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // An ai-assist feature lockdown (AGL-1510/1532) reads as a pause, not\n // as \"AI request failed\" — a provider incident or a cost runaway is\n // something WE turned off, and saying so keeps the user's content out\n // of the story.\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.text) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n Aglyn.canvas.updateNodeProps(node, {\n ...current?.props,\n [effectiveTarget]: payload.text,\n // Rich-text elements render `html` over `children`; drop it so the\n // rewrite is what shows (the user can re-format inline afterwards).\n ...(effectiveTarget === 'children' ? { html: undefined } : {}),\n })\n setNode(null)\n enqueueSnackbar('Text rewritten — undo restores the original', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n node,\n instruction,\n busy,\n user,\n effectiveTarget,\n orgId,\n hostId,\n copyModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n const handleGenerateSection = useCallback(() => {\n // The permission first (AGL-2927) — a section is a generation, so this\n // door sells under `ai.generate`; see `handleRewrite`.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.generate) {\n return void enqueueSnackbar(AI_GENERATE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: same gate as `handleRewrite`.\n if (!orgReady) {\n return void enqueueSnackbar('Checking your plan — try again in a moment', {\n variant: 'info',\n persist: false,\n })\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(assistNotOnPlanText(), {\n variant: 'warning',\n persist: false,\n })\n }\n setSectionPrompt('')\n setSectionOpen(true)\n }, [org, orgReady, aiPermissions, enqueueSnackbar])\n\n const handleSectionConfirm = useCallback(async () => {\n if (!sectionPrompt.trim() || busy) return\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n mode: 'section',\n instruction: sectionPrompt.trim(),\n ...(sectionModel.model ? { model: sectionModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // Same pause notice on the section-generation door (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.section?.rootId) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n const nested = portableDefinitionToNested(\n payload.section.rootId,\n payload.section.nodes,\n )\n if (!nested) {\n return void enqueueSnackbar('AI returned an empty section', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n // Graft through the preset path: ids regenerate, history is saved,\n // and the subtree lands at the end of the canvas root.\n const stamped = (function stamp(node: any): any {\n const schema = Aglyn.components.getSchema(node.componentId)\n return {\n ...node,\n ...(schema?.pluginId ? { pluginId: schema.pluginId } : {}),\n nodes: (node.nodes ?? []).map(stamp),\n }\n })(nested)\n Aglyn.canvas.addNodeFromPreset(\n { type: Aglyn.NodeType.PRESET, data: stamped } as any,\n Aglyn.canvas.rootNode as any,\n )\n setSectionOpen(false)\n enqueueSnackbar('Section added — undo removes it', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n sectionPrompt,\n busy,\n user,\n orgId,\n hostId,\n sectionModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n // A REFUSED key publishes no callback (AGL-2927): the besigner controls\n // render only when the callback exists, so the refusal removes\n // the button rather than dimming it. A PENDING answer keeps both\n // callbacks, and each holds with a notice when pressed — a control that\n // vanishes and reappears while the read lands is worse than one that is\n // briefly inert (the `useHostRole` argument, applied here).\n const refusedUse = aiPermissions?.loaded === true && !aiPermissions.use\n const refusedGenerate =\n aiPermissions?.loaded === true && !aiPermissions.generate\n const contextValue = useMemo(\n () => ({\n ...(refusedUse ? {} : { onRewrite: handleRewrite }),\n ...(refusedGenerate ? {} : { onGenerateSection: handleGenerateSection }),\n }),\n [handleRewrite, handleGenerateSection, refusedUse, refusedGenerate],\n )\n\n return (\n <AiAssistActionsContext.Provider value={contextValue}>\n {children}\n <Dialog\n open={Boolean(node)}\n onClose={() => (busy ? null : setNode(null))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Rewrite with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe how the text should change — tone, length, message.'}\n </Typography>\n {textTargets.length > 1 ? (\n <TextField\n select\n label=\"Field\"\n value={effectiveTarget}\n onChange={(event) => setTargetProp(event.target.value)}\n size=\"small\"\n disabled={busy}\n >\n {textTargets.map((item) => (\n <MenuItem key={item.prop} value={item.prop}>\n {item.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n label=\"Instruction\"\n placeholder=\"e.g. Make it punchier and half as long\"\n value={instruction}\n onChange={(event) => setInstruction(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleConfirm()\n }\n }}\n />\n <AiModelSelector choice={copyModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setNode(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!instruction.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleConfirm}\n >\n {busy ? 'Rewriting…' : 'Rewrite'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={sectionOpen}\n onClose={() => (busy ? null : setSectionOpen(false))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Generate a section with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe the section — e.g. \"hero with headline, subtext, ' +\n 'and a call-to-action button\" or \"three-column feature grid\".'}\n </Typography>\n <TextField\n label=\"Section\"\n value={sectionPrompt}\n onChange={(event) => setSectionPrompt(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleSectionConfirm()\n }\n }}\n />\n <AiModelSelector choice={sectionModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setSectionOpen(false)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!sectionPrompt.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleSectionConfirm}\n >\n {busy ? 'Generating…' : 'Generate'}\n </Button>\n </DialogActions>\n </Dialog>\n </AiAssistActionsContext.Provider>\n )\n}\nAiAssistProvider.displayName = 'AiAssistProvider'\n\nexport default AiAssistProvider\n"],"names":["Aglyn","checkEntitlement","lockdownRefusalText","parseLockdownRefusal","portableDefinitionToNested","AiAssistActionsContext","assistNotOnPlanText","useSnackbar","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","TextField","Typography","useCallback","useMemo","useState","useUser","authorizedFetch","AiModelSelector","AiUsageStrip","useAiModelChoice","usePublishAiUsageMeter","PERMISSIONS_PENDING","AI_USE_REFUSED","AI_GENERATE_REFUSED","AiAssistProvider","props","textTargets","org","orgReady","orgId","hostId","aiPermissions","children","enqueueSnackbar","data","user","node","setNode","instruction","setInstruction","targetProp","setTargetProp","busy","setBusy","sectionOpen","setSectionOpen","sectionPrompt","setSectionPrompt","copyModel","surface","kind","sectionModel","publishMeter","schema","componentSchema","textEditable","flags","FEATURE_FLAG","DISABLED","ENABLED","attributes","filter","field","component","FieldComponentType","TEXT_FIELD","TEXTAREA","map","prop","String","name","label","handleRewrite","target","loaded","variant","persist","use","effectiveTarget","some","item","handleConfirm","trim","current","canvas","toJSON","nodes","$id","text","response","method","headers","body","JSON","stringify","model","payload","json","meter","locked","status","ok","error","allowDuplicate","updateNodeProps","html","undefined","console","handleGenerateSection","generate","handleSectionConfirm","mode","section","rootId","nested","stamped","stamp","components","getSchema","componentId","pluginId","addNodeFromPreset","type","NodeType","PRESET","rootNode","refusedUse","refusedGenerate","contextValue","onRewrite","onGenerateSection","Provider","value","open","Boolean","onClose","maxWidth","fullWidth","sx","display","flexDirection","gap","pt","color","length","select","onChange","event","size","disabled","placeholder","autoFocus","multiline","minRows","onKeyDown","key","metaKey","ctrlKey","preventDefault","choice","onClick","startIcon","displayName"],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * What the workflows plugin's Automation page zones hand this plugin's
19
+ * widgets.
20
+ *
21
+ * `hostAutomations`, `automationEditor` and `automationRun` are zones the
22
+ * workflows plugin hosts and declares (`registerPluginZone`), with their props
23
+ * on its own zone tokens. A plugin never imports another plugin, so the
24
+ * widgets registered there read the props through the shapes restated here —
25
+ * the zone's props as the host documents them, by the same names. A widget is
26
+ * handed whatever the host passes, so this restates what the host SENDS; every
27
+ * member below is one the host documents in the injection zones reference.
28
+ *
29
+ * Types only: nothing here reaches a bundle.
30
+ */
31
+ /** What the `hostAutomations` zone hands a widget. */
32
+ export interface ConsoleHostAutomationsZoneProps {
33
+ hostId: string;
34
+ /** The org the page names; `undefined` while it resolves. */
35
+ orgId: string | undefined;
36
+ /**
37
+ * Opens the Actions editor on a listed action. `false` when the id names no
38
+ * action the list has read yet.
39
+ */
40
+ openAction: (actionId: string) => boolean;
41
+ }
42
+ /** One automation on the Automation page: an action, or a workflow. */
43
+ export interface ConsoleAutomationTarget {
44
+ type: 'action' | 'workflow';
45
+ id: string;
46
+ name: string;
47
+ }
48
+ /** What the `automationEditor` zone hands a widget. */
49
+ export interface ConsoleAutomationEditorZoneProps {
50
+ hostId: string;
51
+ orgId: string | undefined;
52
+ /** The automation the editor has open, as it is stored. */
53
+ target: ConsoleAutomationTarget;
54
+ }
55
+ /** What the `automationRun` zone hands a widget. */
56
+ export interface ConsoleAutomationRunZoneProps {
57
+ hostId: string;
58
+ orgId: string | undefined;
59
+ /** The automation the run belongs to. */
60
+ target: ConsoleAutomationTarget;
61
+ /** The run's entry in the site's activity log. */
62
+ runId: string;
63
+ }
@@ -0,0 +1,31 @@
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
+ * What the workflows plugin's Automation page zones hand this plugin's
18
+ * widgets.
19
+ *
20
+ * `hostAutomations`, `automationEditor` and `automationRun` are zones the
21
+ * workflows plugin hosts and declares (`registerPluginZone`), with their props
22
+ * on its own zone tokens. A plugin never imports another plugin, so the
23
+ * widgets registered there read the props through the shapes restated here —
24
+ * the zone's props as the host documents them, by the same names. A widget is
25
+ * handed whatever the host passes, so this restates what the host SENDS; every
26
+ * member below is one the host documents in the injection zones reference.
27
+ *
28
+ * Types only: nothing here reaches a bundle.
29
+ */ /** What the `hostAutomations` zone hands a widget. */ /** What the `automationRun` zone hands a widget. */ export { };
30
+
31
+ //# sourceMappingURL=ai-automation-zones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-automation-zones.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/**\n * What the workflows plugin's Automation page zones hand this plugin's\n * widgets.\n *\n * `hostAutomations`, `automationEditor` and `automationRun` are zones the\n * workflows plugin hosts and declares (`registerPluginZone`), with their props\n * on its own zone tokens. A plugin never imports another plugin, so the\n * widgets registered there read the props through the shapes restated here —\n * the zone's props as the host documents them, by the same names. A widget is\n * handed whatever the host passes, so this restates what the host SENDS; every\n * member below is one the host documents in the injection zones reference.\n *\n * Types only: nothing here reaches a bundle.\n */\n\n/** What the `hostAutomations` zone hands a widget. */\nexport interface ConsoleHostAutomationsZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /**\n * Opens the Actions editor on a listed action. `false` when the id names no\n * action the list has read yet.\n */\n openAction: (actionId: string) => boolean\n}\n\n/** One automation on the Automation page: an action, or a workflow. */\nexport interface ConsoleAutomationTarget {\n type: 'action' | 'workflow'\n id: string\n name: string\n}\n\n/** What the `automationEditor` zone hands a widget. */\nexport interface ConsoleAutomationEditorZoneProps {\n hostId: string\n orgId: string | undefined\n /** The automation the editor has open, as it is stored. */\n target: ConsoleAutomationTarget\n}\n\n/** What the `automationRun` zone hands a widget. */\nexport interface ConsoleAutomationRunZoneProps {\n hostId: string\n orgId: string | undefined\n /** The automation the run belongs to. */\n target: ConsoleAutomationTarget\n /** The run's entry in the site's activity log. */\n runId: string\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;CAaC,GAED,oDAAoD,GA2BpD,kDAAkD,GAClD,WAOC"}