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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  43. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  44. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  45. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  46. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  47. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  48. package/src/lib/jobs/ai-job-build-step.js +3 -1
  49. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  51. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  52. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  53. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  54. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  56. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  57. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  58. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  59. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  61. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  62. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  64. package/src/lib/jobs/ai-job-page-language.js +72 -9
  65. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  66. package/src/lib/jobs/ai-job-page-step.js +12 -2
  67. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
  69. package/src/lib/jobs/ai-job-plan-step.js +54 -20
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  72. package/src/lib/jobs/ai-job-site-content.js +443 -0
  73. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  74. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  75. package/src/lib/jobs/ai-job-site-look.js +322 -0
  76. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  77. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  78. package/src/lib/jobs/ai-job-site-step.js +163 -24
  79. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  80. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  81. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  82. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  84. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  85. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  86. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  87. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  89. package/src/lib/layout-language/ai-layout-language.js +92 -9
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  92. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  93. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  94. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  95. package/src/lib/layout-language/ai-layout-store.js +7 -1
  96. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  97. package/src/lib/model/ai-build-progress.js +4 -1
  98. package/src/lib/model/ai-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-edit-job.d.ts +109 -0
  100. package/src/lib/model/ai-edit-job.js +157 -0
  101. package/src/lib/model/ai-edit-job.js.map +1 -0
  102. package/src/lib/model/ai-job-activity.js +1 -0
  103. package/src/lib/model/ai-job-activity.js.map +1 -1
  104. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  105. package/src/lib/model/ai-jobs.types.js.map +1 -1
  106. package/src/lib/model/ai-logic-job.d.ts +18 -1
  107. package/src/lib/model/ai-logic-job.js +24 -3
  108. package/src/lib/model/ai-logic-job.js.map +1 -1
  109. package/src/lib/model/ai-media-credits.d.ts +8 -4
  110. package/src/lib/model/ai-media-credits.js +11 -4
  111. package/src/lib/model/ai-media-credits.js.map +1 -1
  112. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  113. package/src/lib/model/ai-media-kinds.js +98 -0
  114. package/src/lib/model/ai-media-kinds.js.map +1 -0
  115. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  116. package/src/lib/model/ai-site-build-progress.js +28 -11
  117. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  118. package/src/lib/model/ai-site-context.d.ts +111 -0
  119. package/src/lib/model/ai-site-context.js +222 -0
  120. package/src/lib/model/ai-site-context.js.map +1 -0
  121. package/src/lib/model/ai-site-job.d.ts +10 -2
  122. package/src/lib/model/ai-site-job.js +12 -5
  123. package/src/lib/model/ai-site-job.js.map +1 -1
  124. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  125. package/src/lib/model/ai-site-kinds.js +1942 -0
  126. package/src/lib/model/ai-site-kinds.js.map +1 -0
  127. package/src/lib/model/ai-site-look.d.ts +150 -0
  128. package/src/lib/model/ai-site-look.js +1236 -0
  129. package/src/lib/model/ai-site-look.js.map +1 -0
  130. package/src/lib/model/ai-site-memory.d.ts +80 -0
  131. package/src/lib/model/ai-site-memory.js +171 -0
  132. package/src/lib/model/ai-site-memory.js.map +1 -0
  133. package/src/lib/model/ai-site-start.d.ts +10 -1
  134. package/src/lib/model/ai-site-start.js +16 -2
  135. package/src/lib/model/ai-site-start.js.map +1 -1
  136. package/src/lib/model/assist-follow-up.d.ts +92 -0
  137. package/src/lib/model/assist-follow-up.js +128 -0
  138. package/src/lib/model/assist-follow-up.js.map +1 -0
  139. package/src/lib/plugin-config.js +2 -0
  140. package/src/lib/plugin-config.js.map +1 -1
  141. package/src/lib/plugin.js +20 -1
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.d.ts +9 -1
  144. package/src/lib/providers/anthropic.js +143 -9
  145. package/src/lib/providers/anthropic.js.map +1 -1
  146. package/src/lib/providers/catalog.d.ts +37 -7
  147. package/src/lib/providers/catalog.js +103 -11
  148. package/src/lib/providers/catalog.js.map +1 -1
  149. package/src/lib/providers/contract.d.ts +29 -0
  150. package/src/lib/providers/contract.js.map +1 -1
  151. package/src/lib/providers/image-contract.d.ts +22 -1
  152. package/src/lib/providers/image-contract.js +40 -1
  153. package/src/lib/providers/image-contract.js.map +1 -1
  154. package/src/lib/providers/image-endpoints.d.ts +24 -0
  155. package/src/lib/providers/image-endpoints.js +28 -0
  156. package/src/lib/providers/image-endpoints.js.map +1 -0
  157. package/src/lib/providers/model-choice.js +23 -2
  158. package/src/lib/providers/model-choice.js.map +1 -1
  159. package/src/lib/providers/routing.js +18 -0
  160. package/src/lib/providers/routing.js.map +1 -1
  161. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  162. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  163. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  164. package/src/lib/providers/vertex-image.d.ts +13 -27
  165. package/src/lib/providers/vertex-image.js +15 -42
  166. package/src/lib/providers/vertex-image.js.map +1 -1
  167. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  168. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  169. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  170. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  171. package/src/lib/runtime/ai-dev-env.js +72 -0
  172. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  173. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  174. package/src/lib/runtime/ai-dev-replay.js +313 -0
  175. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  176. package/src/lib/runtime/ai-doctrine.js +5 -1
  177. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  178. package/src/lib/runtime/ai-eval-live.js +4 -0
  179. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  180. package/src/lib/runtime/ai-eval.d.ts +13 -1
  181. package/src/lib/runtime/ai-eval.js +84 -1
  182. package/src/lib/runtime/ai-eval.js.map +1 -1
  183. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  184. package/src/lib/runtime/ai-live-batch.js +89 -0
  185. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  186. package/src/lib/runtime/ai-runtime.js +46 -2
  187. package/src/lib/runtime/ai-runtime.js.map +1 -1
  188. package/src/lib/runtime/site-context.d.ts +58 -0
  189. package/src/lib/runtime/site-context.js +127 -0
  190. package/src/lib/runtime/site-context.js.map +1 -0
  191. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  192. package/src/lib/server/ai-admin-conversations.js +197 -0
  193. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  194. package/src/lib/server/ai-media-image.d.ts +5 -5
  195. package/src/lib/server/ai-media-image.js +25 -11
  196. package/src/lib/server/ai-media-image.js.map +1 -1
  197. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  198. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  199. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  200. package/src/lib/server/assist-chat.d.ts +11 -0
  201. package/src/lib/server/assist-chat.js +57 -18
  202. package/src/lib/server/assist-chat.js.map +1 -1
  203. package/src/lib/server/assist-edit-applied.js +16 -3
  204. package/src/lib/server/assist-edit-applied.js.map +1 -1
  205. package/src/lib/server/assist-view-context.d.ts +19 -1
  206. package/src/lib/server/assist-view-context.js +38 -1
  207. package/src/lib/server/assist-view-context.js.map +1 -1
  208. package/src/lib/server.js +10 -0
  209. package/src/lib/server.js.map +1 -1
  210. package/src/lib/subprocessors.js +17 -1
  211. package/src/lib/subprocessors.js.map +1 -1
  212. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  213. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  214. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  215. package/src/lib/usage/assist-usage.d.ts +3 -0
  216. package/src/lib/usage/assist-usage.js +4 -2
  217. package/src/lib/usage/assist-usage.js.map +1 -1
  218. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  219. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  220. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -44,13 +44,14 @@ import { type AiGeneratedImage, type AiImageProvider, type AiImageRequest } from
44
44
  *
45
45
  * ⛔ THE ORDER FOR AGLYN'S OWN PRODUCTION, and no check can see it, so this
46
46
  * paragraph is the gate: a description reaching Vertex AI makes Google LLC a
47
- * recipient of customer content for a purpose `/legal/subprocessors` does not
48
- * name today (its Google rows say database, storage, authentication, backups
49
- * and logging). Publish that row first — the master, then the page, with a
50
- * change-log line; the wording is drafted in
51
- * `docs/legal-drafts/AGL-3602-subprocessors.md` — then declare the host in
52
- * the AI catalog with that date as `publishedOn`, and only then set the two
53
- * variables in production.
47
+ * recipient of customer content. The row naming it — Google LLC (Google Cloud
48
+ * Vertex AI), located "Global — Google selects where requests are processed",
49
+ * because the global endpoint promises no processing location — is declared
50
+ * in the AI catalog
51
+ * (`AI_IMAGE_CATALOG_PROVIDERS`) dated to its change-log entry, with the
52
+ * Privacy Policy §2 sentence beside it (legal v12). Both pages are live
53
+ * before either variable is set in production; the wording is in
54
+ * `docs/legal-drafts/AGL-3602-subprocessors.md`.
54
55
  *
55
56
  * ## What is sent, and nothing else
56
57
  *
@@ -60,8 +61,9 @@ import { type AiGeneratedImage, type AiImageProvider, type AiImageRequest } from
60
61
  *
61
62
  * - `responseModalities: ['IMAGE']` — a picture and no prose, which is also
62
63
  * what lets the model render at the size asked for;
63
- * - `imageConfig: { aspectRatio, imageSize: '1K' }` — the shape, at the
64
- * 1K size the per-picture rate is priced at;
64
+ * - `imageConfig: { aspectRatio, imageSize }` — the shape, at the size the
65
+ * door chose from the workspace's plan: `512` on Free, `1K` on a paid
66
+ * plan (`aiImageSizeForPlan`), each priced at its own per-picture rate;
65
67
  * - `thinkingConfig: { thinkingLevel: 'HIGH' }` — Google's mapping for the
66
68
  * Imagen 4 model this replaces;
67
69
  * - `safetySettings` at `BLOCK_MEDIUM_AND_ABOVE` on harassment, hate,
@@ -73,29 +75,13 @@ import { type AiGeneratedImage, type AiImageProvider, type AiImageRequest } from
73
75
  * photorealistic depictions of children or celebrities that violate them —
74
76
  * whatever is asked. Neither is a setting here.
75
77
  */
76
- export declare const VERTEX_IMAGE_PROJECT_ENV = "AI_IMAGE_VERTEX_PROJECT";
77
- export declare const VERTEX_IMAGE_LOCATION_ENV = "AI_IMAGE_VERTEX_LOCATION";
78
- export declare const VERTEX_IMAGE_MODEL_ENV = "AI_IMAGE_MODEL";
79
- /** The location requests go to when the operator names none: Google's global endpoint. */
80
- export declare const VERTEX_IMAGE_DEFAULT_LOCATION = "global";
81
- /** The model a request runs on when the operator names none. */
82
- export declare const VERTEX_IMAGE_DEFAULT_MODEL = "gemini-3.1-flash-image";
78
+ export { VERTEX_IMAGE_PROJECT_ENV, VERTEX_IMAGE_LOCATION_ENV, VERTEX_IMAGE_MODEL_ENV, VERTEX_IMAGE_DEFAULT_LOCATION, VERTEX_IMAGE_DEFAULT_MODEL, vertexImageProject, vertexImageLocation, vertexImageModel, vertexImageHost, vertexImageUrl, } from './vertex-image-endpoint';
83
79
  /** How long one picture may take, thinking included. */
84
80
  export declare const VERTEX_IMAGE_TIMEOUT_MS = 50000;
85
81
  /** The harm categories every request filters at "block some". */
86
82
  export declare const VERTEX_IMAGE_SAFETY_CATEGORIES: readonly ["HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"];
87
- /** The configured project, or `null` when the provider is off. */
88
- export declare function vertexImageProject(): string | null;
89
- /** The configured location; the global endpoint when unset or malformed. */
90
- export declare function vertexImageLocation(): string;
91
- /** The configured model; the default when unset or malformed. */
92
- export declare function vertexImageModel(): string;
93
- /** The host a request goes to: the global endpoint's, or a region's. */
94
- export declare function vertexImageHost(location?: string): string;
95
- /** The `:generateContent` address for a model. */
96
- export declare function vertexImageUrl(project: string, location: string, model: string): string;
97
83
  /** The request body for one picture, exactly as it is sent. */
98
- export declare function vertexImageRequestBody(request: Pick<AiImageRequest, 'prompt' | 'aspectRatio'>): Record<string, unknown>;
84
+ export declare function vertexImageRequestBody(request: Pick<AiImageRequest, 'prompt' | 'aspectRatio' | 'size'>): Record<string, unknown>;
99
85
  /** One answer, read: its picture, why it was withheld, and what it spent. */
100
86
  export interface VertexImageAnswer {
101
87
  image: AiGeneratedImage | null;
@@ -15,9 +15,10 @@
15
15
  * limitations under the License.
16
16
  */ import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
17
17
  import { getApp } from "firebase-admin/app";
18
- import { AI_IMAGE_DEFAULT_MODEL, AI_IMAGE_VERTEX_PROVIDER_ID } from "./catalog.js";
18
+ import { AI_IMAGE_VERTEX_PROVIDER_ID } from "./catalog.js";
19
19
  import { aiTokenCount, AiUpstreamError } from "./contract.js";
20
20
  import { AI_IMAGE_MAX_COUNT, AiImageSafetyRefusal } from "./image-contract.js";
21
+ import { vertexImageProject, vertexImageLocation, vertexImageModel, vertexImageHost, vertexImageUrl } from "./vertex-image-endpoint.js";
21
22
  /**
22
23
  * Google's Gemini image models on Vertex AI (AGL-3602), behind the image
23
24
  * contract: `POST https://aiplatform.googleapis.com/v1/projects/{project}/
@@ -46,13 +47,14 @@ import { AI_IMAGE_MAX_COUNT, AiImageSafetyRefusal } from "./image-contract.js";
46
47
  *
47
48
  * ⛔ THE ORDER FOR AGLYN'S OWN PRODUCTION, and no check can see it, so this
48
49
  * paragraph is the gate: a description reaching Vertex AI makes Google LLC a
49
- * recipient of customer content for a purpose `/legal/subprocessors` does not
50
- * name today (its Google rows say database, storage, authentication, backups
51
- * and logging). Publish that row first — the master, then the page, with a
52
- * change-log line; the wording is drafted in
53
- * `docs/legal-drafts/AGL-3602-subprocessors.md` — then declare the host in
54
- * the AI catalog with that date as `publishedOn`, and only then set the two
55
- * variables in production.
50
+ * recipient of customer content. The row naming it — Google LLC (Google Cloud
51
+ * Vertex AI), located "Global — Google selects where requests are processed",
52
+ * because the global endpoint promises no processing location — is declared
53
+ * in the AI catalog
54
+ * (`AI_IMAGE_CATALOG_PROVIDERS`) dated to its change-log entry, with the
55
+ * Privacy Policy §2 sentence beside it (legal v12). Both pages are live
56
+ * before either variable is set in production; the wording is in
57
+ * `docs/legal-drafts/AGL-3602-subprocessors.md`.
56
58
  *
57
59
  * ## What is sent, and nothing else
58
60
  *
@@ -62,8 +64,9 @@ import { AI_IMAGE_MAX_COUNT, AiImageSafetyRefusal } from "./image-contract.js";
62
64
  *
63
65
  * - `responseModalities: ['IMAGE']` — a picture and no prose, which is also
64
66
  * what lets the model render at the size asked for;
65
- * - `imageConfig: { aspectRatio, imageSize: '1K' }` — the shape, at the
66
- * 1K size the per-picture rate is priced at;
67
+ * - `imageConfig: { aspectRatio, imageSize }` — the shape, at the size the
68
+ * door chose from the workspace's plan: `512` on Free, `1K` on a paid
69
+ * plan (`aiImageSizeForPlan`), each priced at its own per-picture rate;
67
70
  * - `thinkingConfig: { thinkingLevel: 'HIGH' }` — Google's mapping for the
68
71
  * Imagen 4 model this replaces;
69
72
  * - `safetySettings` at `BLOCK_MEDIUM_AND_ABOVE` on harassment, hate,
@@ -74,11 +77,7 @@ import { AI_IMAGE_MAX_COUNT, AiImageSafetyRefusal } from "./image-contract.js";
74
77
  * its own filters refuse what its policies do not allow — among them
75
78
  * photorealistic depictions of children or celebrities that violate them —
76
79
  * whatever is asked. Neither is a setting here.
77
- */ export const VERTEX_IMAGE_PROJECT_ENV = 'AI_IMAGE_VERTEX_PROJECT';
78
- export const VERTEX_IMAGE_LOCATION_ENV = 'AI_IMAGE_VERTEX_LOCATION';
79
- export const VERTEX_IMAGE_MODEL_ENV = 'AI_IMAGE_MODEL';
80
- /** The location requests go to when the operator names none: Google's global endpoint. */ export const VERTEX_IMAGE_DEFAULT_LOCATION = 'global';
81
- /** The model a request runs on when the operator names none. */ export const VERTEX_IMAGE_DEFAULT_MODEL = AI_IMAGE_DEFAULT_MODEL;
80
+ */ export { VERTEX_IMAGE_PROJECT_ENV, VERTEX_IMAGE_LOCATION_ENV, VERTEX_IMAGE_MODEL_ENV, VERTEX_IMAGE_DEFAULT_LOCATION, VERTEX_IMAGE_DEFAULT_MODEL, vertexImageProject, vertexImageLocation, vertexImageModel, vertexImageHost, vertexImageUrl } from "./vertex-image-endpoint.js";
82
81
  /** How long one picture may take, thinking included. */ export const VERTEX_IMAGE_TIMEOUT_MS = 50000;
83
82
  /** The harm categories every request filters at "block some". */ export const VERTEX_IMAGE_SAFETY_CATEGORIES = [
84
83
  'HARM_CATEGORY_HARASSMENT',
@@ -86,9 +85,6 @@ export const VERTEX_IMAGE_MODEL_ENV = 'AI_IMAGE_MODEL';
86
85
  'HARM_CATEGORY_SEXUALLY_EXPLICIT',
87
86
  'HARM_CATEGORY_DANGEROUS_CONTENT'
88
87
  ];
89
- /** A Google Cloud project id: 6–30 lowercase letters, digits and hyphens. */ const PROJECT_ID = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
90
- /** `global`, or a Vertex AI region such as `us-central1`. */ const LOCATION = /^(global|[a-z]+-[a-z]+[0-9]+)$/;
91
- /** A publisher model id, `gemini-3.1-flash-image`. */ const MODEL_ID = /^[a-z0-9][a-z0-9.-]{1,62}$/;
92
88
  /** The finish reasons that mean the picture was withheld rather than failed. */ const WITHHELD_FINISH = new Set([
93
89
  'SAFETY',
94
90
  'IMAGE_SAFETY',
@@ -101,29 +97,6 @@ export const VERTEX_IMAGE_MODEL_ENV = 'AI_IMAGE_MODEL';
101
97
  'IMAGE_OTHER',
102
98
  'NO_IMAGE'
103
99
  ]);
104
- function envValue(name) {
105
- var _ref;
106
- var _process_env_name;
107
- return (_ref = (_process_env_name = process.env[name]) == null ? void 0 : _process_env_name.trim()) != null ? _ref : '';
108
- }
109
- /** The configured project, or `null` when the provider is off. */ export function vertexImageProject() {
110
- const project = envValue(VERTEX_IMAGE_PROJECT_ENV);
111
- return PROJECT_ID.test(project) ? project : null;
112
- }
113
- /** The configured location; the global endpoint when unset or malformed. */ export function vertexImageLocation() {
114
- const location = envValue(VERTEX_IMAGE_LOCATION_ENV);
115
- return LOCATION.test(location) ? location : VERTEX_IMAGE_DEFAULT_LOCATION;
116
- }
117
- /** The configured model; the default when unset or malformed. */ export function vertexImageModel() {
118
- const model = envValue(VERTEX_IMAGE_MODEL_ENV);
119
- return MODEL_ID.test(model) ? model : VERTEX_IMAGE_DEFAULT_MODEL;
120
- }
121
- /** The host a request goes to: the global endpoint's, or a region's. */ export function vertexImageHost(location = vertexImageLocation()) {
122
- return location === 'global' ? 'aiplatform.googleapis.com' : `${location}-aiplatform.googleapis.com`;
123
- }
124
- /** The `:generateContent` address for a model. */ export function vertexImageUrl(project, location, model) {
125
- return `https://${vertexImageHost(location)}/v1/projects/${project}/locations/${location}` + `/publishers/google/models/${model}:generateContent`;
126
- }
127
100
  /** The request body for one picture, exactly as it is sent. */ export function vertexImageRequestBody(request) {
128
101
  return {
129
102
  contents: [
@@ -143,7 +116,7 @@ function envValue(name) {
143
116
  candidateCount: 1,
144
117
  imageConfig: {
145
118
  aspectRatio: request.aspectRatio,
146
- imageSize: '1K'
119
+ imageSize: request.size
147
120
  },
148
121
  thinkingConfig: {
149
122
  thinkingLevel: 'HIGH'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/vertex-image.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { getApp } from 'firebase-admin/app'\nimport { AI_IMAGE_DEFAULT_MODEL, AI_IMAGE_VERTEX_PROVIDER_ID } from './catalog'\nimport { aiTokenCount, AiUpstreamError, type AiUsage } from './contract'\nimport {\n AI_IMAGE_MAX_COUNT,\n AiImageSafetyRefusal,\n type AiGeneratedImage,\n type AiImageProvider,\n type AiImageRequest,\n type AiImageResult,\n} from './image-contract'\n\n/**\n * Google's Gemini image models on Vertex AI (AGL-3602), behind the image\n * contract: `POST https://aiplatform.googleapis.com/v1/projects/{project}/\n * locations/global/publishers/google/models/{model}:generateContent`.\n *\n * Gemini, not Imagen: Google discontinued the Imagen endpoints on Vertex AI\n * in 2026 and names `gemini-3.1-flash-image` as Imagen 4's replacement, at\n * the HIGH thinking level for the standard Imagen 4 model.\n *\n * ## No key of its own\n *\n * It authenticates as the service account firebase-admin already runs as,\n * through that credential's `getAccessToken()` — whose scopes include\n * `cloud-platform`, the scope Vertex AI asks for — the way the Web Risk and\n * reCAPTCHA admin calls do. The account needs the Vertex AI User role\n * (`roles/aiplatform.user`) on the project named below.\n *\n * ## Off unless an operator turns it on\n *\n * `AI_IMAGE_VERTEX_PROJECT` names the Google Cloud project the requests are\n * billed to, and is the switch: unset, `configured()` is false and the Media\n * door answers that photos are not made here. The console offers the photo\n * mode only when `NEXT_PUBLIC_AI_IMAGE_PHOTOS` is `on`, which a deployment\n * sets beside it. A self-hosted deployment is therefore off until its\n * operator chooses a project.\n *\n * ⛔ THE ORDER FOR AGLYN'S OWN PRODUCTION, and no check can see it, so this\n * paragraph is the gate: a description reaching Vertex AI makes Google LLC a\n * recipient of customer content for a purpose `/legal/subprocessors` does not\n * name today (its Google rows say database, storage, authentication, backups\n * and logging). Publish that row first — the master, then the page, with a\n * change-log line; the wording is drafted in\n * `docs/legal-drafts/AGL-3602-subprocessors.md` — then declare the host in\n * the AI catalog with that date as `publishedOn`, and only then set the two\n * variables in production.\n *\n * ## What is sent, and nothing else\n *\n * The description the member typed and the shape. No account, site,\n * organization or member identifier, no other content of the site and no\n * image. One request per picture (`candidateCount` 1), each asking for:\n *\n * - `responseModalities: ['IMAGE']` — a picture and no prose, which is also\n * what lets the model render at the size asked for;\n * - `imageConfig: { aspectRatio, imageSize: '1K' }` — the shape, at the\n * 1K size the per-picture rate is priced at;\n * - `thinkingConfig: { thinkingLevel: 'HIGH' }` — Google's mapping for the\n * Imagen 4 model this replaces;\n * - `safetySettings` at `BLOCK_MEDIUM_AND_ABOVE` on harassment, hate,\n * sexually explicit and dangerous content — the \"block some\" Imagen\n * defaulted to.\n *\n * Google marks every Gemini image with its invisible SynthID watermark, and\n * its own filters refuse what its policies do not allow — among them\n * photorealistic depictions of children or celebrities that violate them —\n * whatever is asked. Neither is a setting here.\n */\n\nexport const VERTEX_IMAGE_PROJECT_ENV = 'AI_IMAGE_VERTEX_PROJECT'\nexport const VERTEX_IMAGE_LOCATION_ENV = 'AI_IMAGE_VERTEX_LOCATION'\nexport const VERTEX_IMAGE_MODEL_ENV = 'AI_IMAGE_MODEL'\n\n/** The location requests go to when the operator names none: Google's global endpoint. */\nexport const VERTEX_IMAGE_DEFAULT_LOCATION = 'global'\n\n/** The model a request runs on when the operator names none. */\nexport const VERTEX_IMAGE_DEFAULT_MODEL = AI_IMAGE_DEFAULT_MODEL\n\n/** How long one picture may take, thinking included. */\nexport const VERTEX_IMAGE_TIMEOUT_MS = 50_000\n\n/** The harm categories every request filters at \"block some\". */\nexport const VERTEX_IMAGE_SAFETY_CATEGORIES = [\n 'HARM_CATEGORY_HARASSMENT',\n 'HARM_CATEGORY_HATE_SPEECH',\n 'HARM_CATEGORY_SEXUALLY_EXPLICIT',\n 'HARM_CATEGORY_DANGEROUS_CONTENT',\n] as const\n\n/** A Google Cloud project id: 6–30 lowercase letters, digits and hyphens. */\nconst PROJECT_ID = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/\n/** `global`, or a Vertex AI region such as `us-central1`. */\nconst LOCATION = /^(global|[a-z]+-[a-z]+[0-9]+)$/\n/** A publisher model id, `gemini-3.1-flash-image`. */\nconst MODEL_ID = /^[a-z0-9][a-z0-9.-]{1,62}$/\n\n/** The finish reasons that mean the picture was withheld rather than failed. */\nconst WITHHELD_FINISH = new Set([\n 'SAFETY',\n 'IMAGE_SAFETY',\n 'PROHIBITED_CONTENT',\n 'IMAGE_PROHIBITED_CONTENT',\n 'BLOCKLIST',\n 'SPII',\n 'RECITATION',\n 'IMAGE_RECITATION',\n 'IMAGE_OTHER',\n 'NO_IMAGE',\n])\n\nfunction envValue(name: string): string {\n return process.env[name]?.trim() ?? ''\n}\n\n/** The configured project, or `null` when the provider is off. */\nexport function vertexImageProject(): string | null {\n const project = envValue(VERTEX_IMAGE_PROJECT_ENV)\n return PROJECT_ID.test(project) ? project : null\n}\n\n/** The configured location; the global endpoint when unset or malformed. */\nexport function vertexImageLocation(): string {\n const location = envValue(VERTEX_IMAGE_LOCATION_ENV)\n return LOCATION.test(location) ? location : VERTEX_IMAGE_DEFAULT_LOCATION\n}\n\n/** The configured model; the default when unset or malformed. */\nexport function vertexImageModel(): string {\n const model = envValue(VERTEX_IMAGE_MODEL_ENV)\n return MODEL_ID.test(model) ? model : VERTEX_IMAGE_DEFAULT_MODEL\n}\n\n/** The host a request goes to: the global endpoint's, or a region's. */\nexport function vertexImageHost(location = vertexImageLocation()): string {\n return location === 'global' ? 'aiplatform.googleapis.com' : `${location}-aiplatform.googleapis.com`\n}\n\n/** The `:generateContent` address for a model. */\nexport function vertexImageUrl(project: string, location: string, model: string): string {\n return (\n `https://${vertexImageHost(location)}/v1/projects/${project}/locations/${location}` +\n `/publishers/google/models/${model}:generateContent`\n )\n}\n\n/** The request body for one picture, exactly as it is sent. */\nexport function vertexImageRequestBody(\n request: Pick<AiImageRequest, 'prompt' | 'aspectRatio'>,\n): Record<string, unknown> {\n return {\n contents: [{ role: 'USER', parts: [{ text: request.prompt }] }],\n generationConfig: {\n responseModalities: ['IMAGE'],\n candidateCount: 1,\n imageConfig: { aspectRatio: request.aspectRatio, imageSize: '1K' },\n thinkingConfig: { thinkingLevel: 'HIGH' },\n },\n safetySettings: VERTEX_IMAGE_SAFETY_CATEGORIES.map((category) => ({\n category,\n threshold: 'BLOCK_MEDIUM_AND_ABOVE',\n })),\n }\n}\n\nconst IMAGE_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp'])\n\n/** One answer, read: its picture, why it was withheld, and what it spent. */\nexport interface VertexImageAnswer {\n image: AiGeneratedImage | null\n /** The finish or block reason, when no picture came back. */\n withheld: string | null\n usage: AiUsage\n}\n\n/**\n * The picture in a `:generateContent` answer. A prompt the filter blocked\n * carries `promptFeedback.blockReason`; a picture the filter withheld carries\n * a finish reason such as `IMAGE_SAFETY`; a model that declined answers in\n * text with `STOP` and no picture. All three are a withheld picture.\n */\nexport function parseVertexImageAnswer(body: unknown): VertexImageAnswer {\n const answer = (body ?? {}) as Record<string, any>\n const meta = (answer['usageMetadata'] ?? {}) as Record<string, unknown>\n const usage: AiUsage = {\n inputTokens: aiTokenCount(meta['promptTokenCount']),\n // The thinking only: the picture's own tokens are priced per picture.\n outputTokens: aiTokenCount(meta['thoughtsTokenCount']),\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n const blocked = answer['promptFeedback']?.['blockReason']\n if (typeof blocked === 'string' && blocked) {\n return { image: null, withheld: blocked, usage }\n }\n const candidate = Array.isArray(answer['candidates']) ? answer['candidates'][0] : null\n const parts = Array.isArray(candidate?.content?.parts) ? candidate.content.parts : []\n for (const part of parts) {\n const inline = part?.inlineData\n const data = typeof inline?.data === 'string' ? inline.data : ''\n const mimeType = typeof inline?.mimeType === 'string' ? inline.mimeType : ''\n if (data && IMAGE_TYPES.has(mimeType)) {\n return { image: { base64: data, mimeType }, withheld: null, usage }\n }\n }\n const finish = typeof candidate?.finishReason === 'string' ? candidate.finishReason : ''\n return {\n image: null,\n withheld: WITHHELD_FINISH.has(finish) ? finish : finish ? `${finish}_NO_IMAGE` : 'NO_IMAGE',\n usage,\n }\n}\n\n/** An access token for the platform's own service account, or `null`. */\nasync function serviceAccountAccessToken(): Promise<string | null> {\n // The default app, initialized by the platform's firebase-admin module on\n // first use; its credential is the one Web Risk's token comes from.\n firebaseAdmin.app()\n const credential = getApp().options.credential\n if (!credential) return null\n const token = await credential.getAccessToken()\n return token?.access_token ?? null\n}\n\nexport interface VertexImageProviderOptions {\n fetch?: typeof fetch\n accessToken?: () => Promise<string | null>\n timeoutMs?: number\n}\n\nconst addUsage = (a: AiUsage, b: AiUsage): AiUsage => ({\n inputTokens: a.inputTokens + b.inputTokens,\n outputTokens: a.outputTokens + b.outputTokens,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n})\n\n/** The adapter, with its network and its credential injectable for a spec. */\nexport function createVertexImageProvider(\n options: VertexImageProviderOptions = {},\n): AiImageProvider {\n async function one(\n url: string,\n token: string,\n request: AiImageRequest,\n ): Promise<VertexImageAnswer> {\n let response: Response\n try {\n response = await (options.fetch ?? fetch)(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json; charset=utf-8',\n },\n body: JSON.stringify(vertexImageRequestBody(request)),\n signal: AbortSignal.timeout(options.timeoutMs ?? VERTEX_IMAGE_TIMEOUT_MS),\n })\n } catch {\n // No answer at all — a timeout or a network fault — is ours to retry.\n throw new AiUpstreamError(null, true, null)\n }\n const requestId = response.headers.get('x-goog-request-id') ?? null\n if (!response.ok) {\n const status = response.status\n const detail = await response.text().catch(() => '')\n console.error('vertex image request failed', {\n status,\n requestId,\n detail: detail.slice(0, 500),\n })\n throw new AiUpstreamError(\n status,\n status === 429 || status >= 500,\n requestId,\n status === 401 || status === 403 ? 'credentials' : null,\n )\n }\n return parseVertexImageAnswer(await response.json().catch(() => null))\n }\n\n return {\n id: AI_IMAGE_VERTEX_PROVIDER_ID,\n label: 'Google Vertex AI',\n defaultModel: vertexImageModel,\n configured: () => vertexImageProject() !== null,\n endpointHost: () => vertexImageHost(),\n async generate(request: AiImageRequest): Promise<AiImageResult> {\n const project = vertexImageProject()\n if (!project) throw new AiUpstreamError(null, false, null, 'credentials')\n const token = await (options.accessToken ?? serviceAccountAccessToken)()\n if (!token) throw new AiUpstreamError(null, false, null, 'credentials')\n const url = vertexImageUrl(project, vertexImageLocation(), request.model)\n const count = Math.min(AI_IMAGE_MAX_COUNT, Math.max(1, Math.floor(request.count)))\n // One request per picture, side by side: a Gemini image request answers\n // with one picture, and four in a row would outlast the door.\n const settled = await Promise.allSettled(\n Array.from({ length: count }, () => one(url, token, request)),\n )\n const answers = settled.flatMap((entry) => (entry.status === 'fulfilled' ? [entry.value] : []))\n const failure = settled.find((entry) => entry.status === 'rejected') as\n | PromiseRejectedResult\n | undefined\n const images = answers.flatMap((answer) => (answer.image ? [answer.image] : []))\n const withheld = answers.flatMap((answer) => (answer.withheld ? [answer.withheld] : []))\n const usage = answers.reduce<AiUsage>((sum, answer) => addUsage(sum, answer.usage), {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n })\n if (!images.length) {\n // Every picture failed for us: the fault is the answer. Every picture\n // was withheld: the filter's decline is.\n if (failure && !withheld.length) throw failure.reason\n throw new AiImageSafetyRefusal(withheld)\n }\n return {\n model: request.model,\n images,\n filtered: count - images.length,\n usage,\n }\n },\n }\n}\n\n/** The adapter the Media door uses. */\nexport const vertexImageProvider: AiImageProvider = createVertexImageProvider()\n"],"names":["firebaseAdmin","getApp","AI_IMAGE_DEFAULT_MODEL","AI_IMAGE_VERTEX_PROVIDER_ID","aiTokenCount","AiUpstreamError","AI_IMAGE_MAX_COUNT","AiImageSafetyRefusal","VERTEX_IMAGE_PROJECT_ENV","VERTEX_IMAGE_LOCATION_ENV","VERTEX_IMAGE_MODEL_ENV","VERTEX_IMAGE_DEFAULT_LOCATION","VERTEX_IMAGE_DEFAULT_MODEL","VERTEX_IMAGE_TIMEOUT_MS","VERTEX_IMAGE_SAFETY_CATEGORIES","PROJECT_ID","LOCATION","MODEL_ID","WITHHELD_FINISH","Set","envValue","name","process","env","trim","vertexImageProject","project","test","vertexImageLocation","location","vertexImageModel","model","vertexImageHost","vertexImageUrl","vertexImageRequestBody","request","contents","role","parts","text","prompt","generationConfig","responseModalities","candidateCount","imageConfig","aspectRatio","imageSize","thinkingConfig","thinkingLevel","safetySettings","map","category","threshold","IMAGE_TYPES","parseVertexImageAnswer","body","answer","candidate","meta","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","blocked","image","withheld","Array","isArray","content","part","inline","inlineData","data","mimeType","has","base64","finish","finishReason","serviceAccountAccessToken","app","credential","options","token","getAccessToken","access_token","addUsage","a","b","createVertexImageProvider","one","url","response","fetch","method","headers","Authorization","JSON","stringify","signal","AbortSignal","timeout","timeoutMs","requestId","get","ok","status","detail","catch","console","error","slice","json","id","label","defaultModel","configured","endpointHost","generate","accessToken","count","Math","min","max","floor","settled","Promise","allSettled","from","length","answers","flatMap","entry","value","failure","find","images","reduce","sum","reason","filtered","vertexImageProvider"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/vertex-image.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { getApp } from 'firebase-admin/app'\nimport { AI_IMAGE_VERTEX_PROVIDER_ID } from './catalog'\nimport { aiTokenCount, AiUpstreamError, type AiUsage } from './contract'\nimport {\n AI_IMAGE_MAX_COUNT,\n AiImageSafetyRefusal,\n type AiGeneratedImage,\n type AiImageProvider,\n type AiImageRequest,\n type AiImageResult,\n} from './image-contract'\nimport {\n vertexImageProject,\n vertexImageLocation,\n vertexImageModel,\n vertexImageHost,\n vertexImageUrl,\n} from './vertex-image-endpoint'\n\n/**\n * Google's Gemini image models on Vertex AI (AGL-3602), behind the image\n * contract: `POST https://aiplatform.googleapis.com/v1/projects/{project}/\n * locations/global/publishers/google/models/{model}:generateContent`.\n *\n * Gemini, not Imagen: Google discontinued the Imagen endpoints on Vertex AI\n * in 2026 and names `gemini-3.1-flash-image` as Imagen 4's replacement, at\n * the HIGH thinking level for the standard Imagen 4 model.\n *\n * ## No key of its own\n *\n * It authenticates as the service account firebase-admin already runs as,\n * through that credential's `getAccessToken()` — whose scopes include\n * `cloud-platform`, the scope Vertex AI asks for — the way the Web Risk and\n * reCAPTCHA admin calls do. The account needs the Vertex AI User role\n * (`roles/aiplatform.user`) on the project named below.\n *\n * ## Off unless an operator turns it on\n *\n * `AI_IMAGE_VERTEX_PROJECT` names the Google Cloud project the requests are\n * billed to, and is the switch: unset, `configured()` is false and the Media\n * door answers that photos are not made here. The console offers the photo\n * mode only when `NEXT_PUBLIC_AI_IMAGE_PHOTOS` is `on`, which a deployment\n * sets beside it. A self-hosted deployment is therefore off until its\n * operator chooses a project.\n *\n * ⛔ THE ORDER FOR AGLYN'S OWN PRODUCTION, and no check can see it, so this\n * paragraph is the gate: a description reaching Vertex AI makes Google LLC a\n * recipient of customer content. The row naming it — Google LLC (Google Cloud\n * Vertex AI), located \"Global — Google selects where requests are processed\",\n * because the global endpoint promises no processing location — is declared\n * in the AI catalog\n * (`AI_IMAGE_CATALOG_PROVIDERS`) dated to its change-log entry, with the\n * Privacy Policy §2 sentence beside it (legal v12). Both pages are live\n * before either variable is set in production; the wording is in\n * `docs/legal-drafts/AGL-3602-subprocessors.md`.\n *\n * ## What is sent, and nothing else\n *\n * The description the member typed and the shape. No account, site,\n * organization or member identifier, no other content of the site and no\n * image. One request per picture (`candidateCount` 1), each asking for:\n *\n * - `responseModalities: ['IMAGE']` — a picture and no prose, which is also\n * what lets the model render at the size asked for;\n * - `imageConfig: { aspectRatio, imageSize }` — the shape, at the size the\n * door chose from the workspace's plan: `512` on Free, `1K` on a paid\n * plan (`aiImageSizeForPlan`), each priced at its own per-picture rate;\n * - `thinkingConfig: { thinkingLevel: 'HIGH' }` — Google's mapping for the\n * Imagen 4 model this replaces;\n * - `safetySettings` at `BLOCK_MEDIUM_AND_ABOVE` on harassment, hate,\n * sexually explicit and dangerous content — the \"block some\" Imagen\n * defaulted to.\n *\n * Google marks every Gemini image with its invisible SynthID watermark, and\n * its own filters refuse what its policies do not allow — among them\n * photorealistic depictions of children or celebrities that violate them —\n * whatever is asked. Neither is a setting here.\n */\n\nexport {\n VERTEX_IMAGE_PROJECT_ENV,\n VERTEX_IMAGE_LOCATION_ENV,\n VERTEX_IMAGE_MODEL_ENV,\n VERTEX_IMAGE_DEFAULT_LOCATION,\n VERTEX_IMAGE_DEFAULT_MODEL,\n vertexImageProject,\n vertexImageLocation,\n vertexImageModel,\n vertexImageHost,\n vertexImageUrl,\n} from './vertex-image-endpoint'\n\n/** How long one picture may take, thinking included. */\nexport const VERTEX_IMAGE_TIMEOUT_MS = 50_000\n\n/** The harm categories every request filters at \"block some\". */\nexport const VERTEX_IMAGE_SAFETY_CATEGORIES = [\n 'HARM_CATEGORY_HARASSMENT',\n 'HARM_CATEGORY_HATE_SPEECH',\n 'HARM_CATEGORY_SEXUALLY_EXPLICIT',\n 'HARM_CATEGORY_DANGEROUS_CONTENT',\n] as const\n\n/** The finish reasons that mean the picture was withheld rather than failed. */\nconst WITHHELD_FINISH = new Set([\n 'SAFETY',\n 'IMAGE_SAFETY',\n 'PROHIBITED_CONTENT',\n 'IMAGE_PROHIBITED_CONTENT',\n 'BLOCKLIST',\n 'SPII',\n 'RECITATION',\n 'IMAGE_RECITATION',\n 'IMAGE_OTHER',\n 'NO_IMAGE',\n])\n\n/** The request body for one picture, exactly as it is sent. */\nexport function vertexImageRequestBody(\n request: Pick<AiImageRequest, 'prompt' | 'aspectRatio' | 'size'>,\n): Record<string, unknown> {\n return {\n contents: [{ role: 'USER', parts: [{ text: request.prompt }] }],\n generationConfig: {\n responseModalities: ['IMAGE'],\n candidateCount: 1,\n imageConfig: { aspectRatio: request.aspectRatio, imageSize: request.size },\n thinkingConfig: { thinkingLevel: 'HIGH' },\n },\n safetySettings: VERTEX_IMAGE_SAFETY_CATEGORIES.map((category) => ({\n category,\n threshold: 'BLOCK_MEDIUM_AND_ABOVE',\n })),\n }\n}\n\nconst IMAGE_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp'])\n\n/** One answer, read: its picture, why it was withheld, and what it spent. */\nexport interface VertexImageAnswer {\n image: AiGeneratedImage | null\n /** The finish or block reason, when no picture came back. */\n withheld: string | null\n usage: AiUsage\n}\n\n/**\n * The picture in a `:generateContent` answer. A prompt the filter blocked\n * carries `promptFeedback.blockReason`; a picture the filter withheld carries\n * a finish reason such as `IMAGE_SAFETY`; a model that declined answers in\n * text with `STOP` and no picture. All three are a withheld picture.\n */\nexport function parseVertexImageAnswer(body: unknown): VertexImageAnswer {\n const answer = (body ?? {}) as Record<string, any>\n const meta = (answer['usageMetadata'] ?? {}) as Record<string, unknown>\n const usage: AiUsage = {\n inputTokens: aiTokenCount(meta['promptTokenCount']),\n // The thinking only: the picture's own tokens are priced per picture.\n outputTokens: aiTokenCount(meta['thoughtsTokenCount']),\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n const blocked = answer['promptFeedback']?.['blockReason']\n if (typeof blocked === 'string' && blocked) {\n return { image: null, withheld: blocked, usage }\n }\n const candidate = Array.isArray(answer['candidates']) ? answer['candidates'][0] : null\n const parts = Array.isArray(candidate?.content?.parts) ? candidate.content.parts : []\n for (const part of parts) {\n const inline = part?.inlineData\n const data = typeof inline?.data === 'string' ? inline.data : ''\n const mimeType = typeof inline?.mimeType === 'string' ? inline.mimeType : ''\n if (data && IMAGE_TYPES.has(mimeType)) {\n return { image: { base64: data, mimeType }, withheld: null, usage }\n }\n }\n const finish = typeof candidate?.finishReason === 'string' ? candidate.finishReason : ''\n return {\n image: null,\n withheld: WITHHELD_FINISH.has(finish) ? finish : finish ? `${finish}_NO_IMAGE` : 'NO_IMAGE',\n usage,\n }\n}\n\n/** An access token for the platform's own service account, or `null`. */\nasync function serviceAccountAccessToken(): Promise<string | null> {\n // The default app, initialized by the platform's firebase-admin module on\n // first use; its credential is the one Web Risk's token comes from.\n firebaseAdmin.app()\n const credential = getApp().options.credential\n if (!credential) return null\n const token = await credential.getAccessToken()\n return token?.access_token ?? null\n}\n\nexport interface VertexImageProviderOptions {\n fetch?: typeof fetch\n accessToken?: () => Promise<string | null>\n timeoutMs?: number\n}\n\nconst addUsage = (a: AiUsage, b: AiUsage): AiUsage => ({\n inputTokens: a.inputTokens + b.inputTokens,\n outputTokens: a.outputTokens + b.outputTokens,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n})\n\n/** The adapter, with its network and its credential injectable for a spec. */\nexport function createVertexImageProvider(\n options: VertexImageProviderOptions = {},\n): AiImageProvider {\n async function one(\n url: string,\n token: string,\n request: AiImageRequest,\n ): Promise<VertexImageAnswer> {\n let response: Response\n try {\n response = await (options.fetch ?? fetch)(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json; charset=utf-8',\n },\n body: JSON.stringify(vertexImageRequestBody(request)),\n signal: AbortSignal.timeout(options.timeoutMs ?? VERTEX_IMAGE_TIMEOUT_MS),\n })\n } catch {\n // No answer at all — a timeout or a network fault — is ours to retry.\n throw new AiUpstreamError(null, true, null)\n }\n const requestId = response.headers.get('x-goog-request-id') ?? null\n if (!response.ok) {\n const status = response.status\n const detail = await response.text().catch(() => '')\n console.error('vertex image request failed', {\n status,\n requestId,\n detail: detail.slice(0, 500),\n })\n throw new AiUpstreamError(\n status,\n status === 429 || status >= 500,\n requestId,\n status === 401 || status === 403 ? 'credentials' : null,\n )\n }\n return parseVertexImageAnswer(await response.json().catch(() => null))\n }\n\n return {\n id: AI_IMAGE_VERTEX_PROVIDER_ID,\n label: 'Google Vertex AI',\n defaultModel: vertexImageModel,\n configured: () => vertexImageProject() !== null,\n endpointHost: () => vertexImageHost(),\n async generate(request: AiImageRequest): Promise<AiImageResult> {\n const project = vertexImageProject()\n if (!project) throw new AiUpstreamError(null, false, null, 'credentials')\n const token = await (options.accessToken ?? serviceAccountAccessToken)()\n if (!token) throw new AiUpstreamError(null, false, null, 'credentials')\n const url = vertexImageUrl(project, vertexImageLocation(), request.model)\n const count = Math.min(AI_IMAGE_MAX_COUNT, Math.max(1, Math.floor(request.count)))\n // One request per picture, side by side: a Gemini image request answers\n // with one picture, and four in a row would outlast the door.\n const settled = await Promise.allSettled(\n Array.from({ length: count }, () => one(url, token, request)),\n )\n const answers = settled.flatMap((entry) => (entry.status === 'fulfilled' ? [entry.value] : []))\n const failure = settled.find((entry) => entry.status === 'rejected') as\n | PromiseRejectedResult\n | undefined\n const images = answers.flatMap((answer) => (answer.image ? [answer.image] : []))\n const withheld = answers.flatMap((answer) => (answer.withheld ? [answer.withheld] : []))\n const usage = answers.reduce<AiUsage>((sum, answer) => addUsage(sum, answer.usage), {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n })\n if (!images.length) {\n // Every picture failed for us: the fault is the answer. Every picture\n // was withheld: the filter's decline is.\n if (failure && !withheld.length) throw failure.reason\n throw new AiImageSafetyRefusal(withheld)\n }\n return {\n model: request.model,\n images,\n filtered: count - images.length,\n usage,\n }\n },\n }\n}\n\n/** The adapter the Media door uses. */\nexport const vertexImageProvider: AiImageProvider = createVertexImageProvider()\n"],"names":["firebaseAdmin","getApp","AI_IMAGE_VERTEX_PROVIDER_ID","aiTokenCount","AiUpstreamError","AI_IMAGE_MAX_COUNT","AiImageSafetyRefusal","vertexImageProject","vertexImageLocation","vertexImageModel","vertexImageHost","vertexImageUrl","VERTEX_IMAGE_PROJECT_ENV","VERTEX_IMAGE_LOCATION_ENV","VERTEX_IMAGE_MODEL_ENV","VERTEX_IMAGE_DEFAULT_LOCATION","VERTEX_IMAGE_DEFAULT_MODEL","VERTEX_IMAGE_TIMEOUT_MS","VERTEX_IMAGE_SAFETY_CATEGORIES","WITHHELD_FINISH","Set","vertexImageRequestBody","request","contents","role","parts","text","prompt","generationConfig","responseModalities","candidateCount","imageConfig","aspectRatio","imageSize","size","thinkingConfig","thinkingLevel","safetySettings","map","category","threshold","IMAGE_TYPES","parseVertexImageAnswer","body","answer","candidate","meta","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","blocked","image","withheld","Array","isArray","content","part","inline","inlineData","data","mimeType","has","base64","finish","finishReason","serviceAccountAccessToken","app","credential","options","token","getAccessToken","access_token","addUsage","a","b","createVertexImageProvider","one","url","response","fetch","method","headers","Authorization","JSON","stringify","signal","AbortSignal","timeout","timeoutMs","requestId","get","ok","status","detail","catch","console","error","slice","json","id","label","defaultModel","configured","endpointHost","generate","project","accessToken","model","count","Math","min","max","floor","settled","Promise","allSettled","from","length","answers","flatMap","entry","value","failure","find","images","reduce","sum","reason","filtered","vertexImageProvider"],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiStepKind } from '../providers/catalog';
18
+ import type { AiProvider } from '../providers/contract';
19
+ import { type AiPluginSettings } from '../providers/routing';
20
+ import { type AiBlogPost } from '../tools/ai-blog-post-tool';
21
+ import { type AiValidatedGeneration } from './ai-doctrine';
22
+ import type { AiSystemBlock } from './ai-runtime';
23
+ /**
24
+ * A SITE'S FIRST POSTS (AGL-3676): one post a generation, through the
25
+ * doctrine's generation call and the strict `write_blog_post` tool.
26
+ *
27
+ * Routed as `copy.blog`, the row the entry editor's own "write this post"
28
+ * already asks — its ceiling is one post body in markdown-lite, which is
29
+ * what one pass writes. The rules are cached and byte-identical; the site's
30
+ * words, the posts already written and which post this is ride in the user
31
+ * turn, so no byte of a site sits in the cached prefix.
32
+ */
33
+ /** The step a post is routed as. */
34
+ export declare const AI_BLOG_POST_STEP: AiStepKind;
35
+ /** One post's answer ceiling: the routing row's. */
36
+ export declare const AI_BLOG_POST_MAX_TOKENS: number;
37
+ /** The generation kind a post is asked under, which the doctrine scopes. */
38
+ export declare const AI_BLOG_POST_KIND = "blog-post";
39
+ /** The rules for one post. Byte-identical on every request. */
40
+ export declare const AI_BLOG_POST_INSTRUCTIONS: AiSystemBlock[];
41
+ export interface AiBlogPostPromptInput {
42
+ /** The site's words: the brief and its answers, as a unit's brief carries them. */
43
+ brief: string;
44
+ /** The titles of the posts already written for this blog. */
45
+ earlierTitles: readonly string[];
46
+ /** Which post this is, from 1, of how many. */
47
+ index: number;
48
+ total: number;
49
+ }
50
+ /** The user turn: the site's words, the posts already written, and which post this is. */
51
+ export declare function aiBlogPostPrompt(input: AiBlogPostPromptInput): string;
52
+ export interface GenerateAiBlogPostInput extends AiBlogPostPromptInput {
53
+ /** What a claim or a price may be quoted from: the brief and the site's answers. */
54
+ merchantWords: string;
55
+ settings?: AiPluginSettings;
56
+ model?: string;
57
+ maxTokens?: number;
58
+ signal?: AbortSignal;
59
+ provider?: AiProvider;
60
+ }
61
+ /** Write one post. It writes nothing to the site. */
62
+ export declare function generateAiBlogPost(input: GenerateAiBlogPostInput): Promise<AiValidatedGeneration<AiBlogPost>>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { AI_ROUTING_TABLE } from "../providers/routing.js";
18
+ import { AI_BLOG_POST_LIMITS, AI_BLOG_POST_TOOL, AI_BLOG_POST_TOOL_NAME, checkAiBlogPost } from "../tools/ai-blog-post-tool.js";
19
+ import { runValidatedGeneration } from "./ai-doctrine.js";
20
+ /**
21
+ * A SITE'S FIRST POSTS (AGL-3676): one post a generation, through the
22
+ * doctrine's generation call and the strict `write_blog_post` tool.
23
+ *
24
+ * Routed as `copy.blog`, the row the entry editor's own "write this post"
25
+ * already asks — its ceiling is one post body in markdown-lite, which is
26
+ * what one pass writes. The rules are cached and byte-identical; the site's
27
+ * words, the posts already written and which post this is ride in the user
28
+ * turn, so no byte of a site sits in the cached prefix.
29
+ */ /** The step a post is routed as. */ export const AI_BLOG_POST_STEP = 'copy.blog';
30
+ /** One post's answer ceiling: the routing row's. */ export const AI_BLOG_POST_MAX_TOKENS = AI_ROUTING_TABLE[AI_BLOG_POST_STEP].maxTokens;
31
+ /** The generation kind a post is asked under, which the doctrine scopes. */ export const AI_BLOG_POST_KIND = 'blog-post';
32
+ /** The rules for one post. Byte-identical on every request. */ export const AI_BLOG_POST_INSTRUCTIONS = [
33
+ {
34
+ text: 'You write one of the first posts of a new blog, published as the site’s own words the day the site goes live.\n\n' + `Answer by calling ${AI_BLOG_POST_TOOL_NAME} exactly once. A reply in prose cannot be used.\n\n` + 'Rules:\n' + [
35
+ 'Write about the subject the brief gives, for the readers it names: advice, an explanation, or how the writer approaches it — useful on its own, in general terms where the brief gives no specifics.',
36
+ 'Never invent a fact about the writer or the business: no name, date, place, number, price, rating, review, testimonial, client, award or credential the brief does not give. Say less rather than make one up.',
37
+ 'Never say anything cures, treats, heals or prevents anything, and never promise a health, financial or legal result.',
38
+ `The body is markdown-lite of ${AI_BLOG_POST_LIMITS.bodyMin} to ${AI_BLOG_POST_LIMITS.bodyMax} characters: short paragraphs, ## subheadings, - lists and **bold**. No title line, since the title renders on its own; no links, images, HTML, emoji or square brackets.`,
39
+ `The title is plain and specific, at most ${AI_BLOG_POST_LIMITS.titleMax} characters, and about something no post already written covers.`,
40
+ `The excerpt says in one or two sentences what the post covers, at most ${AI_BLOG_POST_LIMITS.excerptMax} characters; the search description says it in one.`,
41
+ 'Write in the voice the brief asks for, in the language of the brief.'
42
+ ].map((rule)=>`- ${rule}`).join('\n'),
43
+ cacheBreakpoint: true
44
+ }
45
+ ];
46
+ /** The user turn: the site's words, the posts already written, and which post this is. */ export function aiBlogPostPrompt(input) {
47
+ return [
48
+ 'Brief:',
49
+ input.brief.trim(),
50
+ `Posts already written: ${input.earlierTitles.length ? input.earlierTitles.map((title)=>`“${title}”`).join(', ') : '(none)'}`,
51
+ `Write post ${input.index} of ${input.total}.`
52
+ ].join('\n');
53
+ }
54
+ /** Write one post. It writes nothing to the site. */ export function generateAiBlogPost(input) {
55
+ var _input_maxTokens;
56
+ const row = AI_ROUTING_TABLE[AI_BLOG_POST_STEP];
57
+ return runValidatedGeneration(AI_BLOG_POST_KIND, _extends({
58
+ step: AI_BLOG_POST_STEP
59
+ }, input.settings ? {
60
+ settings: input.settings
61
+ } : {}, input.model ? {
62
+ model: input.model
63
+ } : {}, {
64
+ instructions: AI_BLOG_POST_INSTRUCTIONS,
65
+ messages: [
66
+ {
67
+ role: 'user',
68
+ content: aiBlogPostPrompt(input)
69
+ }
70
+ ],
71
+ tool: AI_BLOG_POST_TOOL,
72
+ maxTokens: (_input_maxTokens = input.maxTokens) != null ? _input_maxTokens : AI_BLOG_POST_MAX_TOKENS
73
+ }, row.thinking ? {
74
+ thinking: row.thinking
75
+ } : {}, row.effort ? {
76
+ effort: row.effort
77
+ } : {}, input.signal ? {
78
+ signal: input.signal
79
+ } : {}, input.provider ? {
80
+ provider: input.provider
81
+ } : {}, {
82
+ check: (answer)=>checkAiBlogPost(answer, {
83
+ merchantWords: input.merchantWords,
84
+ earlierTitles: input.earlierTitles
85
+ })
86
+ }));
87
+ }
88
+
89
+ //# sourceMappingURL=ai-blog-post-generation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-blog-post-generation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiStepKind } from '../providers/catalog'\nimport type { AiProvider } from '../providers/contract'\nimport { AI_ROUTING_TABLE, type AiPluginSettings } from '../providers/routing'\nimport {\n AI_BLOG_POST_LIMITS,\n AI_BLOG_POST_TOOL,\n AI_BLOG_POST_TOOL_NAME,\n checkAiBlogPost,\n type AiBlogPost,\n} from '../tools/ai-blog-post-tool'\nimport { runValidatedGeneration, type AiValidatedGeneration } from './ai-doctrine'\nimport type { AiSystemBlock } from './ai-runtime'\n\n/**\n * A SITE'S FIRST POSTS (AGL-3676): one post a generation, through the\n * doctrine's generation call and the strict `write_blog_post` tool.\n *\n * Routed as `copy.blog`, the row the entry editor's own \"write this post\"\n * already asks — its ceiling is one post body in markdown-lite, which is\n * what one pass writes. The rules are cached and byte-identical; the site's\n * words, the posts already written and which post this is ride in the user\n * turn, so no byte of a site sits in the cached prefix.\n */\n\n/** The step a post is routed as. */\nexport const AI_BLOG_POST_STEP: AiStepKind = 'copy.blog'\n\n/** One post's answer ceiling: the routing row's. */\nexport const AI_BLOG_POST_MAX_TOKENS = AI_ROUTING_TABLE[AI_BLOG_POST_STEP].maxTokens\n\n/** The generation kind a post is asked under, which the doctrine scopes. */\nexport const AI_BLOG_POST_KIND = 'blog-post'\n\n/** The rules for one post. Byte-identical on every request. */\nexport const AI_BLOG_POST_INSTRUCTIONS: AiSystemBlock[] = [\n {\n text:\n 'You write one of the first posts of a new blog, published as the site’s own words the day the site goes live.\\n\\n' +\n `Answer by calling ${AI_BLOG_POST_TOOL_NAME} exactly once. A reply in prose cannot be used.\\n\\n` +\n 'Rules:\\n' +\n [\n 'Write about the subject the brief gives, for the readers it names: advice, an explanation, or how the writer approaches it — useful on its own, in general terms where the brief gives no specifics.',\n 'Never invent a fact about the writer or the business: no name, date, place, number, price, rating, review, testimonial, client, award or credential the brief does not give. Say less rather than make one up.',\n 'Never say anything cures, treats, heals or prevents anything, and never promise a health, financial or legal result.',\n `The body is markdown-lite of ${AI_BLOG_POST_LIMITS.bodyMin} to ${AI_BLOG_POST_LIMITS.bodyMax} characters: short paragraphs, ## subheadings, - lists and **bold**. No title line, since the title renders on its own; no links, images, HTML, emoji or square brackets.`,\n `The title is plain and specific, at most ${AI_BLOG_POST_LIMITS.titleMax} characters, and about something no post already written covers.`,\n `The excerpt says in one or two sentences what the post covers, at most ${AI_BLOG_POST_LIMITS.excerptMax} characters; the search description says it in one.`,\n 'Write in the voice the brief asks for, in the language of the brief.',\n ]\n .map((rule) => `- ${rule}`)\n .join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\nexport interface AiBlogPostPromptInput {\n /** The site's words: the brief and its answers, as a unit's brief carries them. */\n brief: string\n /** The titles of the posts already written for this blog. */\n earlierTitles: readonly string[]\n /** Which post this is, from 1, of how many. */\n index: number\n total: number\n}\n\n/** The user turn: the site's words, the posts already written, and which post this is. */\nexport function aiBlogPostPrompt(input: AiBlogPostPromptInput): string {\n return [\n 'Brief:',\n input.brief.trim(),\n `Posts already written: ${input.earlierTitles.length ? input.earlierTitles.map((title) => `“${title}”`).join(', ') : '(none)'}`,\n `Write post ${input.index} of ${input.total}.`,\n ].join('\\n')\n}\n\nexport interface GenerateAiBlogPostInput extends AiBlogPostPromptInput {\n /** What a claim or a price may be quoted from: the brief and the site's answers. */\n merchantWords: string\n settings?: AiPluginSettings\n model?: string\n maxTokens?: number\n signal?: AbortSignal\n provider?: AiProvider\n}\n\n/** Write one post. It writes nothing to the site. */\nexport function generateAiBlogPost(input: GenerateAiBlogPostInput): Promise<AiValidatedGeneration<AiBlogPost>> {\n const row = AI_ROUTING_TABLE[AI_BLOG_POST_STEP]\n return runValidatedGeneration(AI_BLOG_POST_KIND, {\n step: AI_BLOG_POST_STEP,\n ...(input.settings ? { settings: input.settings } : {}),\n ...(input.model ? { model: input.model } : {}),\n instructions: AI_BLOG_POST_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiBlogPostPrompt(input) }],\n tool: AI_BLOG_POST_TOOL,\n maxTokens: input.maxTokens ?? AI_BLOG_POST_MAX_TOKENS,\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.provider ? { provider: input.provider } : {}),\n check: (answer) =>\n checkAiBlogPost(answer, { merchantWords: input.merchantWords, earlierTitles: input.earlierTitles }),\n })\n}\n"],"names":["AI_ROUTING_TABLE","AI_BLOG_POST_LIMITS","AI_BLOG_POST_TOOL","AI_BLOG_POST_TOOL_NAME","checkAiBlogPost","runValidatedGeneration","AI_BLOG_POST_STEP","AI_BLOG_POST_MAX_TOKENS","maxTokens","AI_BLOG_POST_KIND","AI_BLOG_POST_INSTRUCTIONS","text","bodyMin","bodyMax","titleMax","excerptMax","map","rule","join","cacheBreakpoint","aiBlogPostPrompt","input","brief","trim","earlierTitles","length","title","index","total","generateAiBlogPost","row","step","settings","model","instructions","messages","role","content","tool","thinking","effort","signal","provider","check","answer","merchantWords"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAID,SAASA,gBAAgB,QAA+B,0BAAsB;AAC9E,SACEC,mBAAmB,EACnBC,iBAAiB,EACjBC,sBAAsB,EACtBC,eAAe,QAEV,gCAA4B;AACnC,SAASC,sBAAsB,QAAoC,mBAAe;AAGlF;;;;;;;;;CASC,GAED,kCAAkC,GAClC,OAAO,MAAMC,oBAAgC,YAAW;AAExD,kDAAkD,GAClD,OAAO,MAAMC,0BAA0BP,gBAAgB,CAACM,kBAAkB,CAACE,SAAS,CAAA;AAEpF,0EAA0E,GAC1E,OAAO,MAAMC,oBAAoB,YAAW;AAE5C,6DAA6D,GAC7D,OAAO,MAAMC,4BAA6C;IACxD;QACEC,MACE,sHACA,CAAC,kBAAkB,EAAER,uBAAuB,mDAAmD,CAAC,GAChG,aACA;YACE;YACA;YACA;YACA,CAAC,6BAA6B,EAAEF,oBAAoBW,OAAO,CAAC,IAAI,EAAEX,oBAAoBY,OAAO,CAAC,yKAAyK,CAAC;YACxQ,CAAC,yCAAyC,EAAEZ,oBAAoBa,QAAQ,CAAC,gEAAgE,CAAC;YAC1I,CAAC,uEAAuE,EAAEb,oBAAoBc,UAAU,CAAC,mDAAmD,CAAC;YAC7J;SACD,CACEC,GAAG,CAAC,CAACC,OAAS,CAAC,EAAE,EAAEA,MAAM,EACzBC,IAAI,CAAC;QACVC,iBAAiB;IACnB;CACD,CAAA;AAYD,wFAAwF,GACxF,OAAO,SAASC,iBAAiBC,KAA4B;IAC3D,OAAO;QACL;QACAA,MAAMC,KAAK,CAACC,IAAI;QAChB,CAAC,uBAAuB,EAAEF,MAAMG,aAAa,CAACC,MAAM,GAAGJ,MAAMG,aAAa,CAACR,GAAG,CAAC,CAACU,QAAU,CAAC,CAAC,EAAEA,MAAM,CAAC,CAAC,EAAER,IAAI,CAAC,QAAQ,UAAU;QAC/H,CAAC,WAAW,EAAEG,MAAMM,KAAK,CAAC,IAAI,EAAEN,MAAMO,KAAK,CAAC,CAAC,CAAC;KAC/C,CAACV,IAAI,CAAC;AACT;AAYA,mDAAmD,GACnD,OAAO,SAASW,mBAAmBR,KAA8B;QASlDA;IARb,MAAMS,MAAM9B,gBAAgB,CAACM,kBAAkB;IAC/C,OAAOD,uBAAuBI,mBAAmB;QAC/CsB,MAAMzB;OACFe,MAAMW,QAAQ,GAAG;QAAEA,UAAUX,MAAMW,QAAQ;IAAC,IAAI,CAAC,GACjDX,MAAMY,KAAK,GAAG;QAAEA,OAAOZ,MAAMY,KAAK;IAAC,IAAI,CAAC;QAC5CC,cAAcxB;QACdyB,UAAU;YAAC;gBAAEC,MAAM;gBAAQC,SAASjB,iBAAiBC;YAAO;SAAE;QAC9DiB,MAAMpC;QACNM,SAAS,GAAEa,mBAAAA,MAAMb,SAAS,YAAfa,mBAAmBd;OAC1BuB,IAAIS,QAAQ,GAAG;QAAEA,UAAUT,IAAIS,QAAQ;IAAC,IAAI,CAAC,GAC7CT,IAAIU,MAAM,GAAG;QAAEA,QAAQV,IAAIU,MAAM;IAAC,IAAI,CAAC,GACvCnB,MAAMoB,MAAM,GAAG;QAAEA,QAAQpB,MAAMoB,MAAM;IAAC,IAAI,CAAC,GAC3CpB,MAAMqB,QAAQ,GAAG;QAAEA,UAAUrB,MAAMqB,QAAQ;IAAC,IAAI,CAAC;QACrDC,OAAO,CAACC,SACNxC,gBAAgBwC,QAAQ;gBAAEC,eAAexB,MAAMwB,aAAa;gBAAErB,eAAeH,MAAMG,aAAa;YAAC;;AAEvG"}
@@ -0,0 +1,53 @@
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
+ * Which development layers this process runs (AGL-3660): the replay cache,
19
+ * the hour-long prompt cache and batch mode, each read from the environment.
20
+ * Kept apart from `ai-dev-replay.ts`, which reads and writes files, so the
21
+ * runtime asks these questions on every request without loading a file
22
+ * system module into a server that will never use one. Every answer is `off`
23
+ * in a deployed server.
24
+ */
25
+ export type AiDevReplayMode = 'off' | 'replay' | 'refresh';
26
+ /** The variable that turns the replay cache on, off, or to refresh. */
27
+ export declare const AI_REPLAY_ENV = "AGLYN_AI_REPLAY";
28
+ /** Where the answers are kept, when not under the repository's `.cache/ai-replay`. */
29
+ export declare const AI_REPLAY_DIR_ENV = "AGLYN_AI_REPLAY_DIR";
30
+ /** The prompt-cache lifetime a development run asks for: `1h` or `5m`. */
31
+ export declare const AI_DEV_CACHE_TTL_ENV = "AGLYN_AI_CACHE_TTL";
32
+ /** The mark the live-eval launcher leaves, which `jest.setup.js` also reads. */
33
+ export declare const AI_LIVE_LAUNCHER_ENV = "AI_EVAL_LIVE_LAUNCHER";
34
+ export type AiDevEnv = Readonly<Record<string, string | undefined>>;
35
+ /** Whether this process is a deployed server, where no development layer may run. */
36
+ export declare function aiIsDeployedRuntime(env?: AiDevEnv): boolean;
37
+ /** The replay mode this process runs under; `off` in any deployed server. */
38
+ export declare function aiDevReplayMode(env?: AiDevEnv): AiDevReplayMode;
39
+ /**
40
+ * The prompt-cache lifetime a development run asks for. An agent re-runs a
41
+ * live eval every ten to forty minutes while it iterates, which is past the
42
+ * default five-minute entry and inside an hour, so under the launcher the
43
+ * breakpoints ask for the hour: the second run reads the prefix the first one
44
+ * wrote instead of writing it again. Production keeps the default — its
45
+ * steps run a minute apart on the beat, inside five minutes — and never reads
46
+ * this: `undefined` there.
47
+ */
48
+ export declare function aiDevCacheTtl(env?: AiDevEnv): '1h' | undefined;
49
+ export declare function aiWarnIfReplayAskedInDeployment(env?: AiDevEnv): void;
50
+ /** The variable that turns batch mode on (`runtime/ai-live-batch.ts`). */
51
+ export declare const AI_LIVE_BATCH_ENV = "AGLYN_LIVE_AI_BATCH";
52
+ /** Whether this process batches its live requests. */
53
+ export declare function aiLiveBatchWanted(env?: AiDevEnv): boolean;
@@ -0,0 +1,72 @@
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
+ * Which development layers this process runs (AGL-3660): the replay cache,
18
+ * the hour-long prompt cache and batch mode, each read from the environment.
19
+ * Kept apart from `ai-dev-replay.ts`, which reads and writes files, so the
20
+ * runtime asks these questions on every request without loading a file
21
+ * system module into a server that will never use one. Every answer is `off`
22
+ * in a deployed server.
23
+ */ /** The variable that turns the replay cache on, off, or to refresh. */ export const AI_REPLAY_ENV = 'AGLYN_AI_REPLAY';
24
+ /** Where the answers are kept, when not under the repository's `.cache/ai-replay`. */ export const AI_REPLAY_DIR_ENV = 'AGLYN_AI_REPLAY_DIR';
25
+ /** The prompt-cache lifetime a development run asks for: `1h` or `5m`. */ export const AI_DEV_CACHE_TTL_ENV = 'AGLYN_AI_CACHE_TTL';
26
+ /** The mark the live-eval launcher leaves, which `jest.setup.js` also reads. */ export const AI_LIVE_LAUNCHER_ENV = 'AI_EVAL_LIVE_LAUNCHER';
27
+ /** Whether this process is a deployed server, where no development layer may run. */ export function aiIsDeployedRuntime(env = process.env) {
28
+ return env['NODE_ENV'] === 'production' || env['VERCEL_ENV'] === 'production' || env['VERCEL_ENV'] === 'preview' || Boolean(env['K_SERVICE']);
29
+ }
30
+ /** Whether this process is a development run that asked for the live provider through the launcher. */ function underLiveLauncher(env) {
31
+ return Boolean(env[AI_LIVE_LAUNCHER_ENV]);
32
+ }
33
+ /** The replay mode this process runs under; `off` in any deployed server. */ export function aiDevReplayMode(env = process.env) {
34
+ var _env_AI_REPLAY_ENV;
35
+ if (aiIsDeployedRuntime(env)) return 'off';
36
+ const raw = String((_env_AI_REPLAY_ENV = env[AI_REPLAY_ENV]) != null ? _env_AI_REPLAY_ENV : '').trim().toLowerCase();
37
+ if (raw === 'refresh') return 'refresh';
38
+ if (raw === '1' || raw === 'on' || raw === 'true' || raw === 'replay') return 'replay';
39
+ if (raw === '0' || raw === 'off' || raw === 'false') return 'off';
40
+ return underLiveLauncher(env) ? 'replay' : 'off';
41
+ }
42
+ /**
43
+ * The prompt-cache lifetime a development run asks for. An agent re-runs a
44
+ * live eval every ten to forty minutes while it iterates, which is past the
45
+ * default five-minute entry and inside an hour, so under the launcher the
46
+ * breakpoints ask for the hour: the second run reads the prefix the first one
47
+ * wrote instead of writing it again. Production keeps the default — its
48
+ * steps run a minute apart on the beat, inside five minutes — and never reads
49
+ * this: `undefined` there.
50
+ */ export function aiDevCacheTtl(env = process.env) {
51
+ var _env_AI_DEV_CACHE_TTL_ENV;
52
+ if (aiIsDeployedRuntime(env)) return undefined;
53
+ const raw = String((_env_AI_DEV_CACHE_TTL_ENV = env[AI_DEV_CACHE_TTL_ENV]) != null ? _env_AI_DEV_CACHE_TTL_ENV : '').trim().toLowerCase();
54
+ if (raw === '1h') return '1h';
55
+ if (raw === '5m') return undefined;
56
+ return underLiveLauncher(env) ? '1h' : undefined;
57
+ }
58
+ /** A deployed server that was handed the variable says so once, and runs without it. */ let warnedDeployed = false;
59
+ export function aiWarnIfReplayAskedInDeployment(env = process.env) {
60
+ if (warnedDeployed || !aiIsDeployedRuntime(env)) return;
61
+ if (!env[AI_REPLAY_ENV] && !env[AI_DEV_CACHE_TTL_ENV]) return;
62
+ warnedDeployed = true;
63
+ console.error(`[ai] ${AI_REPLAY_ENV} / ${AI_DEV_CACHE_TTL_ENV} are development-only and are ignored in a deployed server`);
64
+ }
65
+ /** The variable that turns batch mode on (`runtime/ai-live-batch.ts`). */ export const AI_LIVE_BATCH_ENV = 'AGLYN_LIVE_AI_BATCH';
66
+ /** Whether this process batches its live requests. */ export function aiLiveBatchWanted(env = process.env) {
67
+ var _env_AI_LIVE_BATCH_ENV;
68
+ if (aiIsDeployedRuntime(env)) return false;
69
+ return /^(?:1|true|yes|on)$/i.test(String((_env_AI_LIVE_BATCH_ENV = env[AI_LIVE_BATCH_ENV]) != null ? _env_AI_LIVE_BATCH_ENV : '').trim());
70
+ }
71
+
72
+ //# sourceMappingURL=ai-dev-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-dev-env.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 * Which development layers this process runs (AGL-3660): the replay cache,\n * the hour-long prompt cache and batch mode, each read from the environment.\n * Kept apart from `ai-dev-replay.ts`, which reads and writes files, so the\n * runtime asks these questions on every request without loading a file\n * system module into a server that will never use one. Every answer is `off`\n * in a deployed server.\n */\n\nexport type AiDevReplayMode = 'off' | 'replay' | 'refresh'\n\n/** The variable that turns the replay cache on, off, or to refresh. */\nexport const AI_REPLAY_ENV = 'AGLYN_AI_REPLAY'\n/** Where the answers are kept, when not under the repository's `.cache/ai-replay`. */\nexport const AI_REPLAY_DIR_ENV = 'AGLYN_AI_REPLAY_DIR'\n/** The prompt-cache lifetime a development run asks for: `1h` or `5m`. */\nexport const AI_DEV_CACHE_TTL_ENV = 'AGLYN_AI_CACHE_TTL'\n/** The mark the live-eval launcher leaves, which `jest.setup.js` also reads. */\nexport const AI_LIVE_LAUNCHER_ENV = 'AI_EVAL_LIVE_LAUNCHER'\n\nexport type AiDevEnv = Readonly<Record<string, string | undefined>>\n\n/** Whether this process is a deployed server, where no development layer may run. */\nexport function aiIsDeployedRuntime(env: AiDevEnv = process.env): boolean {\n return (\n env['NODE_ENV'] === 'production' ||\n env['VERCEL_ENV'] === 'production' ||\n env['VERCEL_ENV'] === 'preview' ||\n Boolean(env['K_SERVICE'])\n )\n}\n\n/** Whether this process is a development run that asked for the live provider through the launcher. */\nfunction underLiveLauncher(env: AiDevEnv): boolean {\n return Boolean(env[AI_LIVE_LAUNCHER_ENV])\n}\n\n/** The replay mode this process runs under; `off` in any deployed server. */\nexport function aiDevReplayMode(env: AiDevEnv = process.env): AiDevReplayMode {\n if (aiIsDeployedRuntime(env)) return 'off'\n const raw = String(env[AI_REPLAY_ENV] ?? '').trim().toLowerCase()\n if (raw === 'refresh') return 'refresh'\n if (raw === '1' || raw === 'on' || raw === 'true' || raw === 'replay') return 'replay'\n if (raw === '0' || raw === 'off' || raw === 'false') return 'off'\n return underLiveLauncher(env) ? 'replay' : 'off'\n}\n\n/**\n * The prompt-cache lifetime a development run asks for. An agent re-runs a\n * live eval every ten to forty minutes while it iterates, which is past the\n * default five-minute entry and inside an hour, so under the launcher the\n * breakpoints ask for the hour: the second run reads the prefix the first one\n * wrote instead of writing it again. Production keeps the default — its\n * steps run a minute apart on the beat, inside five minutes — and never reads\n * this: `undefined` there.\n */\nexport function aiDevCacheTtl(env: AiDevEnv = process.env): '1h' | undefined {\n if (aiIsDeployedRuntime(env)) return undefined\n const raw = String(env[AI_DEV_CACHE_TTL_ENV] ?? '').trim().toLowerCase()\n if (raw === '1h') return '1h'\n if (raw === '5m') return undefined\n return underLiveLauncher(env) ? '1h' : undefined\n}\n\n/** A deployed server that was handed the variable says so once, and runs without it. */\nlet warnedDeployed = false\nexport function aiWarnIfReplayAskedInDeployment(env: AiDevEnv = process.env): void {\n if (warnedDeployed || !aiIsDeployedRuntime(env)) return\n if (!env[AI_REPLAY_ENV] && !env[AI_DEV_CACHE_TTL_ENV]) return\n warnedDeployed = true\n console.error(\n `[ai] ${AI_REPLAY_ENV} / ${AI_DEV_CACHE_TTL_ENV} are development-only and are ignored in a deployed server`,\n )\n}\n\n/** The variable that turns batch mode on (`runtime/ai-live-batch.ts`). */\nexport const AI_LIVE_BATCH_ENV = 'AGLYN_LIVE_AI_BATCH'\n\n/** Whether this process batches its live requests. */\nexport function aiLiveBatchWanted(env: AiDevEnv = process.env): boolean {\n if (aiIsDeployedRuntime(env)) return false\n return /^(?:1|true|yes|on)$/i.test(String(env[AI_LIVE_BATCH_ENV] ?? '').trim())\n}\n"],"names":["AI_REPLAY_ENV","AI_REPLAY_DIR_ENV","AI_DEV_CACHE_TTL_ENV","AI_LIVE_LAUNCHER_ENV","aiIsDeployedRuntime","env","process","Boolean","underLiveLauncher","aiDevReplayMode","raw","String","trim","toLowerCase","aiDevCacheTtl","undefined","warnedDeployed","aiWarnIfReplayAskedInDeployment","console","error","AI_LIVE_BATCH_ENV","aiLiveBatchWanted","test"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAID,qEAAqE,GACrE,OAAO,MAAMA,gBAAgB,kBAAiB;AAC9C,oFAAoF,GACpF,OAAO,MAAMC,oBAAoB,sBAAqB;AACtD,wEAAwE,GACxE,OAAO,MAAMC,uBAAuB,qBAAoB;AACxD,8EAA8E,GAC9E,OAAO,MAAMC,uBAAuB,wBAAuB;AAI3D,mFAAmF,GACnF,OAAO,SAASC,oBAAoBC,MAAgBC,QAAQD,GAAG;IAC7D,OACEA,GAAG,CAAC,WAAW,KAAK,gBACpBA,GAAG,CAAC,aAAa,KAAK,gBACtBA,GAAG,CAAC,aAAa,KAAK,aACtBE,QAAQF,GAAG,CAAC,YAAY;AAE5B;AAEA,qGAAqG,GACrG,SAASG,kBAAkBH,GAAa;IACtC,OAAOE,QAAQF,GAAG,CAACF,qBAAqB;AAC1C;AAEA,2EAA2E,GAC3E,OAAO,SAASM,gBAAgBJ,MAAgBC,QAAQD,GAAG;QAEtCA;IADnB,IAAID,oBAAoBC,MAAM,OAAO;IACrC,MAAMK,MAAMC,QAAON,qBAAAA,GAAG,CAACL,cAAc,YAAlBK,qBAAsB,IAAIO,IAAI,GAAGC,WAAW;IAC/D,IAAIH,QAAQ,WAAW,OAAO;IAC9B,IAAIA,QAAQ,OAAOA,QAAQ,QAAQA,QAAQ,UAAUA,QAAQ,UAAU,OAAO;IAC9E,IAAIA,QAAQ,OAAOA,QAAQ,SAASA,QAAQ,SAAS,OAAO;IAC5D,OAAOF,kBAAkBH,OAAO,WAAW;AAC7C;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASS,cAAcT,MAAgBC,QAAQD,GAAG;QAEpCA;IADnB,IAAID,oBAAoBC,MAAM,OAAOU;IACrC,MAAML,MAAMC,QAAON,4BAAAA,GAAG,CAACH,qBAAqB,YAAzBG,4BAA6B,IAAIO,IAAI,GAAGC,WAAW;IACtE,IAAIH,QAAQ,MAAM,OAAO;IACzB,IAAIA,QAAQ,MAAM,OAAOK;IACzB,OAAOP,kBAAkBH,OAAO,OAAOU;AACzC;AAEA,sFAAsF,GACtF,IAAIC,iBAAiB;AACrB,OAAO,SAASC,gCAAgCZ,MAAgBC,QAAQD,GAAG;IACzE,IAAIW,kBAAkB,CAACZ,oBAAoBC,MAAM;IACjD,IAAI,CAACA,GAAG,CAACL,cAAc,IAAI,CAACK,GAAG,CAACH,qBAAqB,EAAE;IACvDc,iBAAiB;IACjBE,QAAQC,KAAK,CACX,CAAC,KAAK,EAAEnB,cAAc,GAAG,EAAEE,qBAAqB,0DAA0D,CAAC;AAE/G;AAEA,wEAAwE,GACxE,OAAO,MAAMkB,oBAAoB,sBAAqB;AAEtD,oDAAoD,GACpD,OAAO,SAASC,kBAAkBhB,MAAgBC,QAAQD,GAAG;QAEjBA;IAD1C,IAAID,oBAAoBC,MAAM,OAAO;IACrC,OAAO,uBAAuBiB,IAAI,CAACX,QAAON,yBAAAA,GAAG,CAACe,kBAAkB,YAAtBf,yBAA0B,IAAIO,IAAI;AAC9E"}