@aglyn/plugins-ai 1.0.0-beta.244 → 1.0.0-beta.247

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 (173) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +7 -1
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +21 -0
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-product-zones.d.ts +3 -1
  7. package/src/lib/components/ai-product-zones.js.map +1 -1
  8. package/src/lib/components/ai-products-hub-card.component.js +1 -1
  9. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  10. package/src/lib/components/ai-products-proposals.component.js +17 -9
  11. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  12. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  13. package/src/lib/components/ai-site-start-card.component.js +98 -23
  14. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  15. package/src/lib/components/ai-upsell-dialog.component.js +1 -1
  16. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  17. package/src/lib/declarations.server.js +12 -0
  18. package/src/lib/declarations.server.js.map +1 -1
  19. package/src/lib/jobs/ai-build-capabilities.js +216 -2
  20. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  21. package/src/lib/jobs/ai-job-build-step.d.ts +3 -1
  22. package/src/lib/jobs/ai-job-build-step.js +124 -18
  23. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
  25. package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
  26. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
  27. package/src/lib/jobs/ai-job-form-dataset.d.ts +8 -5
  28. package/src/lib/jobs/ai-job-form-dataset.js +28 -10
  29. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
  30. package/src/lib/jobs/ai-job-form-step.d.ts +20 -4
  31. package/src/lib/jobs/ai-job-form-step.js +98 -28
  32. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  33. package/src/lib/jobs/ai-job-layout-language.d.ts +6 -2
  34. package/src/lib/jobs/ai-job-layout-language.js +29 -5
  35. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  36. package/src/lib/jobs/ai-job-layout-step.js +4 -1
  37. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  38. package/src/lib/jobs/ai-job-media-step.d.ts +239 -0
  39. package/src/lib/jobs/ai-job-media-step.js +610 -0
  40. package/src/lib/jobs/ai-job-media-step.js.map +1 -0
  41. package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
  42. package/src/lib/jobs/ai-job-navigation-step.js +439 -0
  43. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
  44. package/src/lib/jobs/ai-job-page-language.d.ts +4 -1
  45. package/src/lib/jobs/ai-job-page-language.js +30 -5
  46. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  47. package/src/lib/jobs/ai-job-page-sections.js +5 -4
  48. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  49. package/src/lib/jobs/ai-job-page-step.js +3 -2
  50. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  51. package/src/lib/jobs/ai-job-plan-step.d.ts +14 -3
  52. package/src/lib/jobs/ai-job-plan-step.js +87 -20
  53. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
  55. package/src/lib/jobs/ai-job-products-step.js +2 -2
  56. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-settings-step.d.ts +268 -0
  58. package/src/lib/jobs/ai-job-settings-step.js +690 -0
  59. package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
  60. package/src/lib/jobs/ai-job-site-content.d.ts +22 -8
  61. package/src/lib/jobs/ai-job-site-content.js +33 -15
  62. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-datasets.d.ts +3 -1
  64. package/src/lib/jobs/ai-job-site-datasets.js +13 -5
  65. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  66. package/src/lib/jobs/ai-job-site-events.d.ts +148 -0
  67. package/src/lib/jobs/ai-job-site-events.js +313 -0
  68. package/src/lib/jobs/ai-job-site-events.js.map +1 -0
  69. package/src/lib/jobs/ai-job-site-step.d.ts +12 -4
  70. package/src/lib/jobs/ai-job-site-step.js +132 -26
  71. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  72. package/src/lib/jobs/ai-job-tracking-step.d.ts +184 -0
  73. package/src/lib/jobs/ai-job-tracking-step.js +501 -0
  74. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -0
  75. package/src/lib/jobs/ai-layout-stock-photos.d.ts +21 -0
  76. package/src/lib/jobs/ai-layout-stock-photos.js +41 -4
  77. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  78. package/src/lib/jobs/ai-site-publish.d.ts +62 -4
  79. package/src/lib/jobs/ai-site-publish.js +202 -56
  80. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  81. package/src/lib/jobs/ai-stock-photo-picker.d.ts +24 -0
  82. package/src/lib/jobs/ai-stock-photo-picker.js +108 -0
  83. package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -0
  84. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.d.ts +37 -0
  85. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js +47 -0
  86. package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js.map +1 -0
  87. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.d.ts +21 -0
  88. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js +128 -0
  89. package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js.map +1 -0
  90. package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -7
  91. package/src/lib/layout-language/ai-layout-compiler.js +62 -42
  92. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  93. package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
  94. package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
  95. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
  96. package/src/lib/layout-language/ai-layout-frame.d.ts +24 -5
  97. package/src/lib/layout-language/ai-layout-frame.js +93 -20
  98. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  99. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  100. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  101. package/src/lib/layout-language/ai-layout-labels.d.ts +70 -0
  102. package/src/lib/layout-language/ai-layout-labels.js +162 -0
  103. package/src/lib/layout-language/ai-layout-labels.js.map +1 -0
  104. package/src/lib/layout-language/ai-layout-links.js +5 -2
  105. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  106. package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
  107. package/src/lib/layout-language/ai-layout-listings.js +66 -5
  108. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  109. package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
  110. package/src/lib/layout-language/ai-layout-tree.js +17 -0
  111. package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
  112. package/src/lib/model/ai-build-event-page.d.ts +70 -0
  113. package/src/lib/model/ai-build-event-page.js +145 -0
  114. package/src/lib/model/ai-build-event-page.js.map +1 -0
  115. package/src/lib/model/ai-build-job.d.ts +44 -0
  116. package/src/lib/model/ai-build-job.js +141 -13
  117. package/src/lib/model/ai-build-job.js.map +1 -1
  118. package/src/lib/model/ai-build-plan.d.ts +39 -3
  119. package/src/lib/model/ai-build-plan.js +59 -5
  120. package/src/lib/model/ai-build-plan.js.map +1 -1
  121. package/src/lib/model/ai-credit-estimate.d.ts +52 -0
  122. package/src/lib/model/ai-credit-estimate.js +51 -0
  123. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  124. package/src/lib/model/ai-job-activity.js +6 -1
  125. package/src/lib/model/ai-job-activity.js.map +1 -1
  126. package/src/lib/model/ai-jobs.types.d.ts +18 -2
  127. package/src/lib/model/ai-jobs.types.js +6 -1
  128. package/src/lib/model/ai-jobs.types.js.map +1 -1
  129. package/src/lib/model/ai-layout-menus.d.ts +104 -0
  130. package/src/lib/model/ai-layout-menus.js +518 -0
  131. package/src/lib/model/ai-layout-menus.js.map +1 -0
  132. package/src/lib/model/ai-plan-capabilities.d.ts +8 -0
  133. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  134. package/src/lib/model/ai-products.d.ts +31 -1
  135. package/src/lib/model/ai-products.js +34 -1
  136. package/src/lib/model/ai-products.js.map +1 -1
  137. package/src/lib/model/ai-site-inventory.d.ts +17 -0
  138. package/src/lib/model/ai-site-inventory.js +5 -0
  139. package/src/lib/model/ai-site-inventory.js.map +1 -1
  140. package/src/lib/model/ai-site-job.d.ts +12 -2
  141. package/src/lib/model/ai-site-job.js +39 -12
  142. package/src/lib/model/ai-site-job.js.map +1 -1
  143. package/src/lib/model/ai-site-store-pages.js +1 -1
  144. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  145. package/src/lib/runtime/ai-doctrine-validators.d.ts +78 -3
  146. package/src/lib/runtime/ai-doctrine-validators.js +219 -31
  147. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  148. package/src/lib/runtime/ai-doctrine.d.ts +6 -1
  149. package/src/lib/runtime/ai-doctrine.js +16 -7
  150. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  151. package/src/lib/runtime/ai-eval-live.d.ts +1 -1
  152. package/src/lib/runtime/ai-eval-live.js +1 -1
  153. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  154. package/src/lib/runtime/ai-palette.d.ts +3 -1
  155. package/src/lib/runtime/ai-palette.generated.js +499 -11
  156. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  157. package/src/lib/runtime/ai-palette.js +5 -3
  158. package/src/lib/runtime/ai-palette.js.map +1 -1
  159. package/src/lib/runtime/ai-products-generation.d.ts +4 -2
  160. package/src/lib/runtime/ai-products-generation.js +12 -6
  161. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  162. package/src/lib/runtime/ai-template-examples.js +8 -2
  163. package/src/lib/runtime/ai-template-examples.js.map +1 -1
  164. package/src/lib/runtime/site-inventory.d.ts +12 -0
  165. package/src/lib/runtime/site-inventory.js +29 -1
  166. package/src/lib/runtime/site-inventory.js.map +1 -1
  167. package/src/lib/server.js +20 -0
  168. package/src/lib/server.js.map +1 -1
  169. package/src/lib/testing/stand-in-dataset-index.js +37 -3
  170. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
  171. package/src/lib/tools/ai-products-tool.d.ts +3 -1
  172. package/src/lib/tools/ai-products-tool.js +15 -5
  173. package/src/lib/tools/ai-products-tool.js.map +1 -1
package/src/lib/server.js CHANGED
@@ -35,6 +35,11 @@ import { registerAiBuildCapabilities } from "./jobs/ai-build-capabilities.js";
35
35
  import { registerAiWorkflowJob } from "./jobs/ai-job-workflow-step.js";
36
36
  import { registerAiLogicJob } from "./jobs/ai-job-logic-step.js";
37
37
  import { registerAiEditJob } from "./jobs/ai-job-edit-step.js";
38
+ import { registerAiNavigationJob } from "./jobs/ai-job-navigation-step.js";
39
+ import { registerAiDuplicateJob } from "./jobs/ai-job-duplicate-step.js";
40
+ import { registerAiSettingsJob } from "./jobs/ai-job-settings-step.js";
41
+ import { registerAiMediaJob } from "./jobs/ai-job-media-step.js";
42
+ import { registerAiTrackingJob } from "./jobs/ai-job-tracking-step.js";
38
43
  import { registerAiInsightJob } from "./jobs/ai-job-insight-step.js";
39
44
  import { registerAiFigureReaders } from "./insights/ai-figure-readers.js";
40
45
  import { registerAiTextGenerator } from "./server/plugin-text-generation.js";
@@ -124,6 +129,21 @@ export * from "./runtime/ai-runtime.js";
124
129
  // A change to a page or a layout the site has, from a description, saved
125
130
  // where no visitor sees it — the Assist edit rung run as a job (AGL-3616).
126
131
  registerAiEditJob();
132
+ // A change to a site's header or footer menu, made by code from a build's
133
+ // item and saved beside the live layout (AGL-3728).
134
+ registerAiNavigationJob();
135
+ // A copy of something a site already has, through core's duplicate — the
136
+ // console's own Duplicate… — as a build's item (AGL-3740).
137
+ registerAiDuplicateJob();
138
+ // A change to a site's settings — its name, search defaults, icons,
139
+ // language, time zone and contact details — saved live (AGL-3748).
140
+ registerAiSettingsJob();
141
+ // Folders, tags, alt text and names of files already in a site's media
142
+ // library, as its Details drawer saves them, live (AGL-3752).
143
+ registerAiMediaJob();
144
+ // The site's analytics and advertising tags, as Setup → Tracking sets
145
+ // them, saved live (AGL-3751).
146
+ registerAiTrackingJob();
127
147
  // Product copy, and a store's products, categories and discounts from a
128
148
  // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).
129
149
  registerAiProductsJob();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiJobsNotify } from './jobs/ai-jobs-notify'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiBuildJob } from './jobs/ai-job-build-step'\nimport { registerAiBuildCapabilities } from './jobs/ai-build-capabilities'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiLogicJob } from './jobs/ai-job-logic-step'\nimport { registerAiEditJob } from './jobs/ai-job-edit-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as createAiMediaImages } from './server/ai-media-image'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as createAiExperimentVersions } from './server/ai-experiment-versions'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminConversations } from './server/ai-admin-conversations'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { POST as aiAdminCredits } from './server/ai-admin-credits'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The person who started a job is told when it needs them, finishes or\n // stops (AGL-3593), from the write that changed it.\n registerAiJobsNotify()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Site functions and variables proposed from a description, and functions\n // explained, as proposals the logic editor opens unsaved (AGL-3603).\n registerAiLogicJob()\n // A change to a page or a layout the site has, from a description, saved\n // where no visitor sees it — the Assist edit rung run as a job (AGL-3616).\n registerAiEditJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // A build from one request (AGL-3616): pages, creations and other\n // plugins' drafts, through the steps above and the writers those plugins\n // register, settled item by item. What this plugin lets a build make is\n // registered on the core's capability contract like any plugin's.\n registerAiBuildCapabilities()\n registerAiBuildJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n // A machine's route: Cloud Scheduler calls it on the cron secret with no\n // site and no member, and the route checks that secret and the AI release\n // flag itself. Unmarked, it was counted in the dispatcher's per-address\n // write bucket and refused with a 429 at 14:00Z on 2026-10-09.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest }, { machine: true })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // A page's or a section's A/B variants as draft versions (AGL-3603): the\n // published version copied once per proposed variant, the variant's copy\n // put in, stored unpublished for the experiment editor to pin.\n registerPluginApiRoute('ai/experiments/versions', { web: createAiExperimentVersions })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n // Pictures from a description, in Media (AGL-3602): photos and SVG\n // illustrations, stored through the media upload route. POST only — the\n // button draws from the shell's gates and asks nothing until it is used.\n registerPluginApiRoute('ai/media/images', { web: createAiMediaImages })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // What an org asked Aglyn AI and what it answered (AGL-3675): one page at a\n // time, read on request, each page an audited access.\n registerPluginApiRoute('ai/admin/conversations', { web: aiAdminConversations })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n // Credits given back to a workspace's band or a Free owner's allowance\n // (AGL-3595): compensation for a fault of ours, bounded by what the month\n // used, keyed against a double-click, and audited.\n registerPluginApiRoute('ai/admin/credits', { web: aiAdminCredits })\n}\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { aiPlanEntitlements } from './plan-entitlements'\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiJobsNotify","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiBuildJob","registerAiBuildCapabilities","registerAiWorkflowJob","registerAiLogicJob","registerAiEditJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","resumeAiJob","aiGenerateComponent","createAiMediaImages","applyAiSeoAudit","createAiExperimentVersions","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminConversations","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiAdminCredits","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String","machine"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiJobsNotify } from './jobs/ai-jobs-notify'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiBuildJob } from './jobs/ai-job-build-step'\nimport { registerAiBuildCapabilities } from './jobs/ai-build-capabilities'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiLogicJob } from './jobs/ai-job-logic-step'\nimport { registerAiEditJob } from './jobs/ai-job-edit-step'\nimport { registerAiNavigationJob } from './jobs/ai-job-navigation-step'\nimport { registerAiDuplicateJob } from './jobs/ai-job-duplicate-step'\nimport { registerAiSettingsJob } from './jobs/ai-job-settings-step'\nimport { registerAiMediaJob } from './jobs/ai-job-media-step'\nimport { registerAiTrackingJob } from './jobs/ai-job-tracking-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as createAiMediaImages } from './server/ai-media-image'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as createAiExperimentVersions } from './server/ai-experiment-versions'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminConversations } from './server/ai-admin-conversations'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { POST as aiAdminCredits } from './server/ai-admin-credits'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The person who started a job is told when it needs them, finishes or\n // stops (AGL-3593), from the write that changed it.\n registerAiJobsNotify()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Site functions and variables proposed from a description, and functions\n // explained, as proposals the logic editor opens unsaved (AGL-3603).\n registerAiLogicJob()\n // A change to a page or a layout the site has, from a description, saved\n // where no visitor sees it — the Assist edit rung run as a job (AGL-3616).\n registerAiEditJob()\n // A change to a site's header or footer menu, made by code from a build's\n // item and saved beside the live layout (AGL-3728).\n registerAiNavigationJob()\n // A copy of something a site already has, through core's duplicate — the\n // console's own Duplicate… — as a build's item (AGL-3740).\n registerAiDuplicateJob()\n // A change to a site's settings — its name, search defaults, icons,\n // language, time zone and contact details — saved live (AGL-3748).\n registerAiSettingsJob()\n // Folders, tags, alt text and names of files already in a site's media\n // library, as its Details drawer saves them, live (AGL-3752).\n registerAiMediaJob()\n // The site's analytics and advertising tags, as Setup → Tracking sets\n // them, saved live (AGL-3751).\n registerAiTrackingJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // A build from one request (AGL-3616): pages, creations and other\n // plugins' drafts, through the steps above and the writers those plugins\n // register, settled item by item. What this plugin lets a build make is\n // registered on the core's capability contract like any plugin's.\n registerAiBuildCapabilities()\n registerAiBuildJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n // A machine's route: Cloud Scheduler calls it on the cron secret with no\n // site and no member, and the route checks that secret and the AI release\n // flag itself. Unmarked, it was counted in the dispatcher's per-address\n // write bucket and refused with a 429 at 14:00Z on 2026-10-09.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest }, { machine: true })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // A page's or a section's A/B variants as draft versions (AGL-3603): the\n // published version copied once per proposed variant, the variant's copy\n // put in, stored unpublished for the experiment editor to pin.\n registerPluginApiRoute('ai/experiments/versions', { web: createAiExperimentVersions })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n // Pictures from a description, in Media (AGL-3602): photos and SVG\n // illustrations, stored through the media upload route. POST only — the\n // button draws from the shell's gates and asks nothing until it is used.\n registerPluginApiRoute('ai/media/images', { web: createAiMediaImages })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // What an org asked Aglyn AI and what it answered (AGL-3675): one page at a\n // time, read on request, each page an audited access.\n registerPluginApiRoute('ai/admin/conversations', { web: aiAdminConversations })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n // Credits given back to a workspace's band or a Free owner's allowance\n // (AGL-3595): compensation for a fault of ours, bounded by what the month\n // used, keyed against a double-click, and audited.\n registerPluginApiRoute('ai/admin/credits', { web: aiAdminCredits })\n}\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { aiPlanEntitlements } from './plan-entitlements'\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiJobsNotify","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiBuildJob","registerAiBuildCapabilities","registerAiWorkflowJob","registerAiLogicJob","registerAiEditJob","registerAiNavigationJob","registerAiDuplicateJob","registerAiSettingsJob","registerAiMediaJob","registerAiTrackingJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","resumeAiJob","aiGenerateComponent","createAiMediaImages","applyAiSeoAudit","createAiExperimentVersions","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminConversations","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiAdminCredits","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String","machine"],"mappings":"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"}
@@ -25,7 +25,10 @@ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-ser
25
25
  * datasets only, narrowed to a site to those shared with it, each with its
26
26
  * fields (`{ id, name, type }` and a `customType` where one rides it, in
27
27
  * the dataset page's order) and its
28
- * `visibleTo`. What the data plugin itself answers is held in its own spec.
28
+ * `visibleTo`; narrowed to a site, the site's record page for it as
29
+ * `recordPage` (`{ screenId, base, slugField }`, or `null`, AGL-3747) read off
30
+ * `hosts/{hostId}/recordPages`. What the data plugin itself answers is held in
31
+ * its own spec.
29
32
  */ const OWNER = 'data';
30
33
  const str = (value)=>typeof value === 'string' ? value.trim() : '';
31
34
  /**
@@ -81,6 +84,34 @@ function record(id, data) {
81
84
  /** Registers it, reading through `firestore`. */ export function standInDatasetIndex(firestore) {
82
85
  const datasets = (orgId)=>firestore.collection('orgs').doc(orgId).collection('datasets');
83
86
  const shared = (one, hostId)=>one && (!hostId || visibleToHost(one.facts['visibleTo'], hostId)) ? one : null;
87
+ const recordPages = async (hostId)=>{
88
+ const snapshot = await firestore.collection('hosts').doc(hostId).collection('recordPages').get();
89
+ const pages = new Map();
90
+ for (const doc of snapshot.docs){
91
+ var _doc_data;
92
+ const data = (_doc_data = doc.data()) != null ? _doc_data : {};
93
+ const base = str(data['base']).replace(/^\/+|\/+$/g, '');
94
+ if (!str(data['datasetId']) || !base || !str(data['slugField']) || pages.has(str(data['datasetId']))) continue;
95
+ pages.set(str(data['datasetId']), {
96
+ screenId: doc.id,
97
+ base,
98
+ slugField: str(data['slugField'])
99
+ });
100
+ }
101
+ return pages;
102
+ };
103
+ const withPages = async (records, hostId)=>{
104
+ if (!hostId || !records.length) return records;
105
+ const pages = await recordPages(hostId);
106
+ return records.map((one)=>{
107
+ var _pages_get;
108
+ return _extends({}, one, {
109
+ facts: _extends({}, one.facts, {
110
+ recordPage: (_pages_get = pages.get(one.id)) != null ? _pages_get : null
111
+ })
112
+ });
113
+ });
114
+ };
84
115
  registerPluginRecordIndex('dataset', {
85
116
  async list ({ orgId, hostId, limit }) {
86
117
  if (!orgId) return {
@@ -90,14 +121,17 @@ function record(id, data) {
90
121
  const snapshot = await datasets(orgId).get();
91
122
  const records = snapshot.docs.map((doc)=>shared(record(doc.id, doc.data()), hostId)).filter((one)=>one !== null);
92
123
  return {
93
- records: records.slice(0, limit),
124
+ records: await withPages(records.slice(0, limit), hostId),
94
125
  truncated: records.length > limit
95
126
  };
96
127
  },
97
128
  async get ({ orgId, hostId, id }) {
98
129
  if (!orgId) return null;
99
130
  const snapshot = await datasets(orgId).doc(id).get();
100
- return snapshot.exists ? shared(record(snapshot.id, snapshot.data()), hostId) : null;
131
+ const one = snapshot.exists ? shared(record(snapshot.id, snapshot.data()), hostId) : null;
132
+ return one ? (await withPages([
133
+ one
134
+ ], hostId))[0] : null;
101
135
  }
102
136
  }, {
103
137
  pluginId: OWNER
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-dataset-index.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 { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n registerPluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * The `dataset` index the plugin that keeps the workspace's datasets\n * publishes (AGL-3080), stood in over a spec's own Firestore double — this\n * plugin may not load the data plugin that registers the real one. It answers\n * in the shape the data plugin's `dataset-record-index.ts` documents: live\n * datasets only, narrowed to a site to those shared with it, each with its\n * fields (`{ id, name, type }` and a `customType` where one rides it, in\n * the dataset page's order) and its\n * `visibleTo`. What the data plugin itself answers is held in its own spec.\n */\n\nconst OWNER = 'data'\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** A field as a dataset document stores it, as far as this stand-in reads one. */\ninterface StoredField {\n name?: unknown\n type?: unknown\n customType?: unknown\n}\n\n/**\n * The fields a dataset document declares, in the Data page's order: its\n * model's, or for a dataset from before models, each name in `fields` as a\n * text field — the derivation the data plugin applies.\n */\nfunction storedFields(data: Record<string, unknown>): { order: string[]; fields: Record<string, StoredField> } {\n const model = data['model'] as { order?: unknown; fields?: unknown } | undefined\n if (model && Array.isArray(model.order) && model.order.length && model.fields && typeof model.fields === 'object') {\n return { order: model.order.map(String), fields: model.fields as Record<string, StoredField> }\n }\n const names = Array.isArray(data['fields']) ? data['fields'].map(String) : []\n const titled = (name: string) => {\n const words = name.replace(/_/g, ' ').trim()\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : name\n }\n return {\n order: names,\n fields: Object.fromEntries(names.map((name) => [name, { name: titled(name), type: 'text' }])),\n }\n}\n\nfunction record(id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const model = storedFields(data)\n return {\n id,\n name: str(data['displayName']) || str(data['name']) || id,\n facts: {\n fields: model.order.map((fieldId) => ({\n id: fieldId,\n name: str(model.fields[fieldId]?.name) || fieldId,\n type: String(model.fields[fieldId]?.type ?? 'text'),\n ...(str(model.fields[fieldId]?.customType) ? { customType: str(model.fields[fieldId]?.customType) } : {}),\n })),\n visibleTo: Array.isArray(data['visibleTo']) ? data['visibleTo'] : [],\n },\n }\n}\n\n/** Registers it, reading through `firestore`. */\nexport function standInDatasetIndex(firestore: FirebaseFirestore.Firestore): void {\n const datasets = (orgId: string) => firestore.collection('orgs').doc(orgId).collection('datasets')\n const shared = (one: PluginIndexedRecord | null, hostId: string | null | undefined) =>\n one && (!hostId || visibleToHost(one.facts['visibleTo'] as string[], hostId)) ? one : null\n registerPluginRecordIndex(\n 'dataset',\n {\n async list({ orgId, hostId, limit }) {\n if (!orgId) return { records: [], truncated: false }\n const snapshot = await datasets(orgId).get()\n const records = snapshot.docs\n .map((doc) => shared(record(doc.id, doc.data()), hostId))\n .filter((one): one is PluginIndexedRecord => one !== null)\n return { records: records.slice(0, limit), truncated: records.length > limit }\n },\n async get({ orgId, hostId, id }) {\n if (!orgId) return null\n const snapshot = await datasets(orgId).doc(id).get()\n return snapshot.exists ? shared(record(snapshot.id, snapshot.data()), hostId) : null\n },\n },\n { pluginId: OWNER },\n )\n}\n\n/** Forgets it, so the next spec starts with nothing keeping datasets. */\nexport function removeStandInDatasetIndex(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["visibleToHost","registerPluginRecordIndex","unregisterPluginServices","OWNER","str","value","trim","storedFields","data","model","Array","isArray","order","length","fields","map","String","names","titled","name","words","replace","charAt","toUpperCase","slice","Object","fromEntries","type","record","id","facts","fieldId","customType","visibleTo","standInDatasetIndex","firestore","datasets","orgId","collection","doc","shared","one","hostId","list","limit","records","truncated","snapshot","get","docs","filter","exists","pluginId","removeStandInDatasetIndex"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-dataset-index.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 { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n registerPluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * The `dataset` index the plugin that keeps the workspace's datasets\n * publishes (AGL-3080), stood in over a spec's own Firestore double — this\n * plugin may not load the data plugin that registers the real one. It answers\n * in the shape the data plugin's `dataset-record-index.ts` documents: live\n * datasets only, narrowed to a site to those shared with it, each with its\n * fields (`{ id, name, type }` and a `customType` where one rides it, in\n * the dataset page's order) and its\n * `visibleTo`; narrowed to a site, the site's record page for it as\n * `recordPage` (`{ screenId, base, slugField }`, or `null`, AGL-3747) read off\n * `hosts/{hostId}/recordPages`. What the data plugin itself answers is held in\n * its own spec.\n */\n\nconst OWNER = 'data'\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** A field as a dataset document stores it, as far as this stand-in reads one. */\ninterface StoredField {\n name?: unknown\n type?: unknown\n customType?: unknown\n}\n\n/**\n * The fields a dataset document declares, in the Data page's order: its\n * model's, or for a dataset from before models, each name in `fields` as a\n * text field — the derivation the data plugin applies.\n */\nfunction storedFields(data: Record<string, unknown>): { order: string[]; fields: Record<string, StoredField> } {\n const model = data['model'] as { order?: unknown; fields?: unknown } | undefined\n if (model && Array.isArray(model.order) && model.order.length && model.fields && typeof model.fields === 'object') {\n return { order: model.order.map(String), fields: model.fields as Record<string, StoredField> }\n }\n const names = Array.isArray(data['fields']) ? data['fields'].map(String) : []\n const titled = (name: string) => {\n const words = name.replace(/_/g, ' ').trim()\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : name\n }\n return {\n order: names,\n fields: Object.fromEntries(names.map((name) => [name, { name: titled(name), type: 'text' }])),\n }\n}\n\nfunction record(id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const model = storedFields(data)\n return {\n id,\n name: str(data['displayName']) || str(data['name']) || id,\n facts: {\n fields: model.order.map((fieldId) => ({\n id: fieldId,\n name: str(model.fields[fieldId]?.name) || fieldId,\n type: String(model.fields[fieldId]?.type ?? 'text'),\n ...(str(model.fields[fieldId]?.customType) ? { customType: str(model.fields[fieldId]?.customType) } : {}),\n })),\n visibleTo: Array.isArray(data['visibleTo']) ? data['visibleTo'] : [],\n },\n }\n}\n\n/** Registers it, reading through `firestore`. */\nexport function standInDatasetIndex(firestore: FirebaseFirestore.Firestore): void {\n const datasets = (orgId: string) => firestore.collection('orgs').doc(orgId).collection('datasets')\n const shared = (one: PluginIndexedRecord | null, hostId: string | null | undefined) =>\n one && (!hostId || visibleToHost(one.facts['visibleTo'] as string[], hostId)) ? one : null\n const recordPages = async (hostId: string) => {\n const snapshot = await firestore.collection('hosts').doc(hostId).collection('recordPages').get()\n const pages = new Map<string, { screenId: string; base: string; slugField: string }>()\n for (const doc of snapshot.docs) {\n const data = doc.data() ?? {}\n const base = str(data['base']).replace(/^\\/+|\\/+$/g, '')\n if (!str(data['datasetId']) || !base || !str(data['slugField']) || pages.has(str(data['datasetId']))) continue\n pages.set(str(data['datasetId']), { screenId: doc.id, base, slugField: str(data['slugField']) })\n }\n return pages\n }\n const withPages = async (records: PluginIndexedRecord[], hostId: string | null | undefined) => {\n if (!hostId || !records.length) return records\n const pages = await recordPages(hostId)\n return records.map((one) => ({ ...one, facts: { ...one.facts, recordPage: pages.get(one.id) ?? null } }))\n }\n registerPluginRecordIndex(\n 'dataset',\n {\n async list({ orgId, hostId, limit }) {\n if (!orgId) return { records: [], truncated: false }\n const snapshot = await datasets(orgId).get()\n const records = snapshot.docs\n .map((doc) => shared(record(doc.id, doc.data()), hostId))\n .filter((one): one is PluginIndexedRecord => one !== null)\n return { records: await withPages(records.slice(0, limit), hostId), truncated: records.length > limit }\n },\n async get({ orgId, hostId, id }) {\n if (!orgId) return null\n const snapshot = await datasets(orgId).doc(id).get()\n const one = snapshot.exists ? shared(record(snapshot.id, snapshot.data()), hostId) : null\n return one ? (await withPages([one], hostId))[0] : null\n },\n },\n { pluginId: OWNER },\n )\n}\n\n/** Forgets it, so the next spec starts with nothing keeping datasets. */\nexport function removeStandInDatasetIndex(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["visibleToHost","registerPluginRecordIndex","unregisterPluginServices","OWNER","str","value","trim","storedFields","data","model","Array","isArray","order","length","fields","map","String","names","titled","name","words","replace","charAt","toUpperCase","slice","Object","fromEntries","type","record","id","facts","fieldId","customType","visibleTo","standInDatasetIndex","firestore","datasets","orgId","collection","doc","shared","one","hostId","recordPages","snapshot","get","pages","Map","docs","base","has","set","screenId","slugField","withPages","records","recordPage","list","limit","truncated","filter","exists","pluginId","removeStandInDatasetIndex"],"mappings":"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"}
@@ -28,7 +28,9 @@ import type { AiGenerationCheckResult } from '../runtime/ai-doctrine';
28
28
  * discounts, from a brief.
29
29
  *
30
30
  * Each schema is kept small, and no field is nullable: "none" is an empty
31
- * string, an empty list or zero. A strict schema accepts no length keyword,
31
+ * string, an empty list or zero. A catalog product's `price` is the one field
32
+ * no answer is refused for: code keeps it in range or drops it to the
33
+ * store's default (`aiCatalogPrice`). A strict schema accepts no length keyword,
32
34
  * so every bound is stated in a description and enforced here. A check
33
35
  * returns a value only when nothing in the answer is broken, and otherwise
34
36
  * the violations and the parts at fault, for the generation call's one
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ import { SEO_LISTING_FIELDS } from "@aglyn/aglyn/app-utils/seo-listing-fields";
17
- import { AI_CATALOG_DESCRIPTION_MAX_CHARS, AI_CATALOG_OPTION_VALUE_MAX_CHARS, AI_CATALOG_OPTION_VALUES_MAX, AI_CATALOG_OPTIONS_MAX, AI_CATALOG_PHOTO_MAX_CHARS, AI_CATALOG_PRODUCTS, AI_CATALOG_TAGS_MAX, AI_CATEGORIES_MAX, AI_CATEGORY_NAME_MAX_CHARS, AI_DISCOUNT_CODE, AI_DISCOUNT_MAX_FIXED_USD, AI_DISCOUNT_MAX_PERCENT, AI_DISCOUNTS_MAX, AI_PRODUCT_CATEGORIES_MAX, AI_PRODUCT_DESCRIPTION_MAX_CHARS, AI_PRODUCT_NAME_MAX_CHARS, AI_PRODUCT_OPTION_NAME_MAX_CHARS, AI_PRODUCT_TAG_MAX_CHARS, AI_PRODUCT_TAGS_MAX, AI_PRODUCT_TYPES, AI_PROPOSAL_WHY_MAX_CHARS } from "../model/ai-products.js";
17
+ import { AI_CATALOG_DESCRIPTION_MAX_CHARS, AI_CATALOG_OPTION_VALUE_MAX_CHARS, AI_CATALOG_OPTION_VALUES_MAX, AI_CATALOG_OPTIONS_MAX, AI_CATALOG_PHOTO_MAX_CHARS, AI_CATALOG_PRICE_USD, AI_CATALOG_PRODUCTS, AI_CATALOG_TAGS_MAX, AI_CATEGORIES_MAX, AI_CATEGORY_NAME_MAX_CHARS, AI_DISCOUNT_CODE, AI_DISCOUNT_MAX_FIXED_USD, AI_DISCOUNT_MAX_PERCENT, AI_DISCOUNTS_MAX, AI_PRODUCT_CATEGORIES_MAX, AI_PRODUCT_DESCRIPTION_MAX_CHARS, AI_PRODUCT_NAME_MAX_CHARS, AI_PRODUCT_OPTION_NAME_MAX_CHARS, AI_PRODUCT_TAG_MAX_CHARS, AI_PRODUCT_TAGS_MAX, AI_PRODUCT_TYPES, AI_PROPOSAL_WHY_MAX_CHARS, aiCatalogPrice } from "../model/ai-products.js";
18
18
  import { findInventedPrices, findStorefrontClaims, storefrontClaimViolations } from "../model/ai-storefront-claims.js";
19
19
  import { detectOffVoiceCopy } from "../runtime/ai-doctrine-validators.js";
20
20
  /**
@@ -28,7 +28,9 @@ import { detectOffVoiceCopy } from "../runtime/ai-doctrine-validators.js";
28
28
  * discounts, from a brief.
29
29
  *
30
30
  * Each schema is kept small, and no field is nullable: "none" is an empty
31
- * string, an empty list or zero. A strict schema accepts no length keyword,
31
+ * string, an empty list or zero. A catalog product's `price` is the one field
32
+ * no answer is refused for: code keeps it in range or drops it to the
33
+ * store's default (`aiCatalogPrice`). A strict schema accepts no length keyword,
32
34
  * so every bound is stated in a description and enforced here. A check
33
35
  * returns a value only when nothing in the answer is broken, and otherwise
34
36
  * the violations and the parts at fault, for the generation call's one
@@ -270,7 +272,8 @@ function fail(findings, at, code, message, value) {
270
272
  'options',
271
273
  'seoTitle',
272
274
  'seoDescription',
273
- 'photo'
275
+ 'photo',
276
+ 'price'
274
277
  ],
275
278
  properties: {
276
279
  name: string(`Distinct within the list, at most ${AI_PRODUCT_NAME_MAX_CHARS} characters.`),
@@ -300,7 +303,11 @@ function fail(findings, at, code, message, value) {
300
303
  },
301
304
  seoTitle: string(`At most ${SEO_TITLE_MAX} characters.`),
302
305
  seoDescription: string(`At most ${SEO_DESCRIPTION_MAX} characters.`),
303
- photo: string(`What the product photo should show, in one sentence of at most ${AI_CATALOG_PHOTO_MAX_CHARS} characters.`)
306
+ photo: string(`What the product photo should show, in one sentence of at most ${AI_CATALOG_PHOTO_MAX_CHARS} characters.`),
307
+ price: {
308
+ type: 'number',
309
+ description: `Its suggested price in US dollars, from ${AI_CATALOG_PRICE_USD.min} to ${AI_CATALOG_PRICE_USD.max}.`
310
+ }
304
311
  }
305
312
  }
306
313
  }
@@ -392,7 +399,10 @@ function fail(findings, at, code, message, value) {
392
399
  options,
393
400
  seoTitle,
394
401
  seoDescription,
395
- photo
402
+ photo,
403
+ // Kept by code, never re-asked (Zach, 2026-10-12): a price that is
404
+ // missing or out of range is `null`, and the store's default stands in.
405
+ price: aiCatalogPrice(entry['price'])
396
406
  });
397
407
  });
398
408
  findings.violations.push(...claimViolations(samples, context.merchantWords), ...detectOffVoiceCopy(samples, null));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-products-tool.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 { SEO_LISTING_FIELDS } from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport {\n AI_CATALOG_DESCRIPTION_MAX_CHARS,\n AI_CATALOG_OPTION_VALUE_MAX_CHARS,\n AI_CATALOG_OPTION_VALUES_MAX,\n AI_CATALOG_OPTIONS_MAX,\n AI_CATALOG_PHOTO_MAX_CHARS,\n AI_CATALOG_PRODUCTS,\n AI_CATALOG_TAGS_MAX,\n AI_CATEGORIES_MAX,\n AI_CATEGORY_NAME_MAX_CHARS,\n AI_DISCOUNT_CODE,\n AI_DISCOUNT_MAX_FIXED_USD,\n AI_DISCOUNT_MAX_PERCENT,\n AI_DISCOUNTS_MAX,\n AI_PRODUCT_CATEGORIES_MAX,\n AI_PRODUCT_DESCRIPTION_MAX_CHARS,\n AI_PRODUCT_NAME_MAX_CHARS,\n AI_PRODUCT_OPTION_NAME_MAX_CHARS,\n AI_PRODUCT_TAG_MAX_CHARS,\n AI_PRODUCT_TAGS_MAX,\n AI_PRODUCT_TYPES,\n AI_PROPOSAL_WHY_MAX_CHARS,\n type AiCatalogProduct,\n type AiCategoriesProposal,\n type AiDiscountKind,\n type AiProductCopy,\n type AiProductOption,\n type AiProductType,\n type AiProposedDiscount,\n} from '../model/ai-products'\nimport {\n findInventedPrices,\n findStorefrontClaims,\n storefrontClaimViolations,\n type AiStorefrontCopySample,\n} from '../model/ai-storefront-claims'\nimport type { AiTool } from '../providers/contract'\nimport type { AiGenerationCheckResult } from '../runtime/ai-doctrine'\nimport { detectOffVoiceCopy, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\n\n/**\n * The strict tools commerce by AI answers through (AGL-2916), and the checks\n * that hold each answer to the commerce editor it proposes for.\n *\n * - `propose_product_copy` — one product's description, search listing, tags,\n * categories and option names.\n * - `propose_catalog` — a store's first products, from a brief.\n * - `propose_categories_and_discounts` — store categories and a first set of\n * discounts, from a brief.\n *\n * Each schema is kept small, and no field is nullable: \"none\" is an empty\n * string, an empty list or zero. A strict schema accepts no length keyword,\n * so every bound is stated in a description and enforced here. A check\n * returns a value only when nothing in the answer is broken, and otherwise\n * the violations and the parts at fault, for the generation call's one\n * re-ask.\n */\n\nexport const AI_PRODUCT_COPY_TOOL_NAME = 'propose_product_copy'\nexport const AI_CATALOG_TOOL_NAME = 'propose_catalog'\nexport const AI_CATEGORIES_TOOL_NAME = 'propose_categories_and_discounts'\n\nconst SEO_TITLE_MAX = SEO_LISTING_FIELDS.title.maxLength\nconst SEO_DESCRIPTION_MAX = SEO_LISTING_FIELDS.description.maxLength\n\nconst string = (description: string) => ({ type: 'string', description })\nconst strings = (description: string) => ({ type: 'array', items: { type: 'string' }, description })\n\n/* ------------------------------------------------------------------------ *\n * Shared reads\n * ------------------------------------------------------------------------ */\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** One line: runs of white space folded to one space. */\nconst line = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\n\n/** Paragraphs: spaces folded within a line, at most one blank line between paragraphs. */\nconst paragraphs = (value: unknown): string =>\n typeof value === 'string'\n ? value\n .replace(/\\r\\n?/g, '\\n')\n .split('\\n')\n .map((part) => part.replace(/[ \\t\\f\\v]+/g, ' ').trim())\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n : ''\n\nconst MARKUP = /<\\/?[a-z][^>]*>/i\nconst MARKDOWN = /(^|\\n)\\s*#{1,6}\\s|\\*\\*[^*\\n]+\\*\\*|__[^_\\n]+__|!\\[[^\\]]*\\]\\(|\\[[^\\]]+\\]\\(https?:/\nconst EMOJI = /\\p{Extended_Pictographic}/u\nconst BRACKET = /[[\\]]/\nconst URL = /\\bhttps?:\\/\\/|\\bwww\\./i\n\ninterface Findings {\n violations: AiDoctrineViolation[]\n offending: Record<string, unknown>\n}\n\nfunction fail(findings: Findings, at: string, code: string, message: string, value: unknown): void {\n findings.violations.push({ rule: null, code, message, paths: [at] })\n if (!(at in findings.offending)) findings.offending[at] = value\n}\n\n/** A piece of copy a shopper reads: there, within its length, and plain text. */\nfunction checkCopyText(\n findings: Findings,\n at: string,\n raw: unknown,\n bounds: { label: string; max: number; multiline?: boolean; brackets?: boolean },\n): string {\n if (typeof raw !== 'string') {\n fail(findings, at, 'type', `${bounds.label} must be text.`, raw)\n return ''\n }\n const value = bounds.multiline ? paragraphs(raw) : line(raw)\n if (!value) {\n fail(findings, at, 'missing', `${bounds.label} is required.`, raw)\n return ''\n }\n if (value.length > bounds.max) {\n fail(findings, at, 'too-long', `${bounds.label} is ${value.length} characters; it may have ${bounds.max}.`, raw)\n }\n if (MARKUP.test(value) || MARKDOWN.test(value)) {\n fail(findings, at, 'markup', `${bounds.label} is plain text, with no markup or formatting marks.`, raw)\n }\n if (EMOJI.test(value)) fail(findings, at, 'emoji', `${bounds.label} holds no emoji.`, raw)\n if (URL.test(value)) fail(findings, at, 'link', `${bounds.label} holds no link.`, raw)\n if (bounds.brackets === false && BRACKET.test(value)) {\n fail(findings, at, 'bracket', `${bounds.label} is published as written, so it marks no missing fact in brackets.`, raw)\n }\n return value\n}\n\n/** Tags a shopper filters by: short, distinct, no `#`, no more than `max`. */\nfunction checkTags(findings: Findings, at: string, raw: unknown, max = AI_PRODUCT_TAGS_MAX): string[] {\n if (!Array.isArray(raw)) {\n fail(findings, at, 'type', 'Tags are a list of words or short phrases.', raw)\n return []\n }\n const seen = new Set<string>()\n const tags: string[] = []\n raw.forEach((entry, index) => {\n const tag = line(entry).replace(/^#+/, '').trim()\n if (!tag) return\n if (tag.length > AI_PRODUCT_TAG_MAX_CHARS || EMOJI.test(tag) || BRACKET.test(tag)) {\n fail(findings, `${at}[${index}]`, 'tag', `A tag is a plain word or phrase of at most ${AI_PRODUCT_TAG_MAX_CHARS} characters.`, entry)\n return\n }\n const key = tag.toLowerCase()\n if (seen.has(key)) return\n seen.add(key)\n tags.push(tag)\n })\n if (tags.length > max) {\n fail(findings, at, 'too-many', `A product carries at most ${max} tags.`, raw)\n }\n return tags\n}\n\n/** The claims and prices a set of copy may not make, as violations. */\nfunction claimViolations(samples: readonly AiStorefrontCopySample[], merchantWords: string): AiDoctrineViolation[] {\n return storefrontClaimViolations(\n findStorefrontClaims(samples, merchantWords),\n findInventedPrices(samples, merchantWords),\n )\n}\n\n/* ------------------------------------------------------------------------ *\n * One product's copy\n * ------------------------------------------------------------------------ */\n\nexport const AI_PRODUCT_COPY_TOOL: AiTool = {\n name: AI_PRODUCT_COPY_TOOL_NAME,\n description: 'Propose the storefront copy for one product. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['description', 'seoTitle', 'seoDescription', 'tags', 'categoryIds', 'optionNames'],\n properties: {\n description: string(\n `The product description shoppers read, in short paragraphs, at most ${AI_PRODUCT_DESCRIPTION_MAX_CHARS} characters.`,\n ),\n seoTitle: string(`The search result headline, published as written, at most ${SEO_TITLE_MAX} characters.`),\n seoDescription: string(`The search result summary, at most ${SEO_DESCRIPTION_MAX} characters.`),\n tags: strings(`At most ${AI_PRODUCT_TAGS_MAX} tags, each at most ${AI_PRODUCT_TAG_MAX_CHARS} characters.`),\n categoryIds: strings(\n `At most ${AI_PRODUCT_CATEGORIES_MAX} category ids from the list you were given; empty when none fits.`,\n ),\n optionNames: strings(\n `One name per option the product has, in order, each at most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters; empty when it has none.`,\n ),\n },\n },\n}\n\nexport interface AiProductCopyCheckContext {\n /** The ids of the site's categories the request listed. */\n categoryIds: readonly string[]\n /** How many options the product has. */\n optionCount: number\n /** The merchant's own words about the product: its name, text and tags. */\n merchantWords: string\n}\n\n/**\n * A product copy answer held to the editor: every field written and within\n * its length, plain text, the categories the request listed, one distinct\n * name per option, and no claim or price the copy may not make.\n */\nexport function checkAiProductCopy(\n answer: Record<string, unknown>,\n context: AiProductCopyCheckContext,\n): AiGenerationCheckResult<AiProductCopy> {\n const findings: Findings = { violations: [], offending: {} }\n const description = checkCopyText(findings, 'description', answer['description'], {\n label: 'The description',\n max: AI_PRODUCT_DESCRIPTION_MAX_CHARS,\n multiline: true,\n })\n const seoTitle = checkCopyText(findings, 'seoTitle', answer['seoTitle'], {\n label: 'The search title',\n max: SEO_TITLE_MAX,\n brackets: false,\n })\n const seoDescription = checkCopyText(findings, 'seoDescription', answer['seoDescription'], {\n label: 'The search description',\n max: SEO_DESCRIPTION_MAX,\n brackets: false,\n })\n const tags = checkTags(findings, 'tags', answer['tags'])\n\n const allowed = new Set(context.categoryIds)\n const categoryIds: string[] = []\n const rawCategories = answer['categoryIds']\n if (!Array.isArray(rawCategories)) {\n fail(findings, 'categoryIds', 'type', 'Categories are a list of ids.', rawCategories)\n } else {\n for (const entry of rawCategories) {\n const id = line(entry)\n if (!id || categoryIds.includes(id)) continue\n if (!allowed.has(id)) {\n fail(findings, 'categoryIds', 'category-unknown', 'Use only the category ids you were given.', rawCategories)\n continue\n }\n categoryIds.push(id)\n }\n if (categoryIds.length > AI_PRODUCT_CATEGORIES_MAX) {\n fail(findings, 'categoryIds', 'too-many', `A product goes in at most ${AI_PRODUCT_CATEGORIES_MAX} categories.`, rawCategories)\n }\n }\n\n const optionNames: string[] = []\n const rawOptions = answer['optionNames']\n if (!Array.isArray(rawOptions)) {\n fail(findings, 'optionNames', 'type', 'Option names are a list.', rawOptions)\n } else {\n if (rawOptions.length !== context.optionCount) {\n fail(\n findings,\n 'optionNames',\n 'option-count',\n `The product has ${context.optionCount} ${context.optionCount === 1 ? 'option' : 'options'}; give one name for each.`,\n rawOptions,\n )\n }\n rawOptions.slice(0, context.optionCount).forEach((entry, index) => {\n const name = line(entry)\n if (!name || name.length > AI_PRODUCT_OPTION_NAME_MAX_CHARS || BRACKET.test(name) || EMOJI.test(name)) {\n fail(findings, `optionNames[${index}]`, 'option-name', `An option name is a plain word or phrase of at most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters.`, entry)\n }\n optionNames.push(name)\n })\n if (new Set(optionNames.map((name) => name.toLowerCase())).size !== optionNames.length) {\n fail(findings, 'optionNames', 'option-duplicate', 'Each option needs its own name.', rawOptions)\n }\n }\n\n const samples: AiStorefrontCopySample[] = [\n { at: 'description', text: description },\n { at: 'seoTitle', text: seoTitle },\n { at: 'seoDescription', text: seoDescription },\n ...tags.map((tag, index) => ({ at: `tags[${index}]`, text: tag })),\n ]\n findings.violations.push(\n ...claimViolations(samples, context.merchantWords),\n ...detectOffVoiceCopy(samples, null),\n )\n const value = findings.violations.length\n ? null\n : { description, seoTitle, seoDescription, tags, categoryIds, optionNames }\n return { value, violations: findings.violations, offending: findings.offending }\n}\n\n/* ------------------------------------------------------------------------ *\n * A store's first products\n * ------------------------------------------------------------------------ */\n\nexport const AI_CATALOG_TOOL: AiTool = {\n name: AI_CATALOG_TOOL_NAME,\n description: 'Propose the products of a new store, each a draft its owner completes. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['products'],\n properties: {\n products: {\n type: 'array',\n description: `Between ${AI_CATALOG_PRODUCTS.min} and ${AI_CATALOG_PRODUCTS.max} products.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'type', 'description', 'tags', 'options', 'seoTitle', 'seoDescription', 'photo'],\n properties: {\n name: string(`Distinct within the list, at most ${AI_PRODUCT_NAME_MAX_CHARS} characters.`),\n type: { type: 'string', enum: [...AI_PRODUCT_TYPES] },\n description: string(`In short paragraphs, at most ${AI_CATALOG_DESCRIPTION_MAX_CHARS} characters.`),\n tags: strings(`At most ${AI_CATALOG_TAGS_MAX} tags, each at most ${AI_PRODUCT_TAG_MAX_CHARS} characters.`),\n options: {\n type: 'array',\n description: `The choices a shopper makes, at most ${AI_CATALOG_OPTIONS_MAX}; empty for a product with none.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'values'],\n properties: {\n name: string(`At most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters.`),\n values: strings(\n `At most ${AI_CATALOG_OPTION_VALUES_MAX} distinct values, each at most ${AI_CATALOG_OPTION_VALUE_MAX_CHARS} characters.`,\n ),\n },\n },\n },\n seoTitle: string(`At most ${SEO_TITLE_MAX} characters.`),\n seoDescription: string(`At most ${SEO_DESCRIPTION_MAX} characters.`),\n photo: string(`What the product photo should show, in one sentence of at most ${AI_CATALOG_PHOTO_MAX_CHARS} characters.`),\n },\n },\n },\n },\n },\n}\n\n/** A catalog answer held to the products editor and the storefront copy rules. */\nexport function checkAiCatalog(\n answer: Record<string, unknown>,\n context: { merchantWords: string },\n): AiGenerationCheckResult<AiCatalogProduct[]> {\n const findings: Findings = { violations: [], offending: {} }\n const raw = answer['products']\n if (!Array.isArray(raw)) {\n fail(findings, 'products', 'type', 'The products are a list.', raw)\n return { value: null, violations: findings.violations, offending: findings.offending }\n }\n if (raw.length < AI_CATALOG_PRODUCTS.min || raw.length > AI_CATALOG_PRODUCTS.max) {\n fail(findings, 'products', 'product-count', `Propose between ${AI_CATALOG_PRODUCTS.min} and ${AI_CATALOG_PRODUCTS.max} products.`, `${raw.length} products`)\n }\n const names = new Set<string>()\n const products: AiCatalogProduct[] = []\n const samples: AiStorefrontCopySample[] = []\n raw.slice(0, AI_CATALOG_PRODUCTS.max).forEach((entry, index) => {\n const at = `products[${index}]`\n if (!isRecord(entry)) {\n fail(findings, at, 'type', 'Each product is an object.', entry)\n return\n }\n const name = checkCopyText(findings, `${at}.name`, entry['name'], {\n label: 'A product name',\n max: AI_PRODUCT_NAME_MAX_CHARS,\n brackets: false,\n })\n if (name && names.has(name.toLowerCase())) {\n fail(findings, `${at}.name`, 'name-duplicate', 'Each product needs its own name.', entry['name'])\n }\n names.add(name.toLowerCase())\n const type = entry['type']\n if (!(AI_PRODUCT_TYPES as readonly unknown[]).includes(type)) {\n fail(findings, `${at}.type`, 'type', 'A product is physical, digital or a service.', type)\n }\n const description = checkCopyText(findings, `${at}.description`, entry['description'], {\n label: 'A product description',\n max: AI_CATALOG_DESCRIPTION_MAX_CHARS,\n multiline: true,\n })\n const tags = checkTags(findings, `${at}.tags`, entry['tags'], AI_CATALOG_TAGS_MAX)\n const options = checkCatalogOptions(findings, `${at}.options`, entry['options'])\n const seoTitle = checkCopyText(findings, `${at}.seoTitle`, entry['seoTitle'], {\n label: 'A search title',\n max: SEO_TITLE_MAX,\n brackets: false,\n })\n const seoDescription = checkCopyText(findings, `${at}.seoDescription`, entry['seoDescription'], {\n label: 'A search description',\n max: SEO_DESCRIPTION_MAX,\n brackets: false,\n })\n const photo = checkCopyText(findings, `${at}.photo`, entry['photo'], {\n label: 'What the photo shows',\n max: AI_CATALOG_PHOTO_MAX_CHARS,\n brackets: false,\n })\n samples.push(\n { at: `${at}.name`, text: name },\n { at: `${at}.description`, text: description },\n { at: `${at}.seoTitle`, text: seoTitle },\n { at: `${at}.seoDescription`, text: seoDescription },\n ...tags.map((tag, tagIndex) => ({ at: `${at}.tags[${tagIndex}]`, text: tag })),\n )\n products.push({\n name,\n type: type as AiProductType,\n description,\n tags,\n options,\n seoTitle,\n seoDescription,\n photo,\n })\n })\n findings.violations.push(...claimViolations(samples, context.merchantWords), ...detectOffVoiceCopy(samples, null))\n return {\n value: findings.violations.length ? null : products,\n violations: findings.violations,\n offending: findings.offending,\n }\n}\n\nfunction checkCatalogOptions(findings: Findings, at: string, raw: unknown): AiProductOption[] {\n if (!Array.isArray(raw)) {\n fail(findings, at, 'type', 'Options are a list.', raw)\n return []\n }\n if (raw.length > AI_CATALOG_OPTIONS_MAX) {\n fail(findings, at, 'too-many', `A proposed product has at most ${AI_CATALOG_OPTIONS_MAX} options.`, raw)\n }\n const options: AiProductOption[] = []\n raw.slice(0, AI_CATALOG_OPTIONS_MAX).forEach((entry, index) => {\n const here = `${at}[${index}]`\n const record = isRecord(entry) ? entry : {}\n const name = line(record['name'])\n if (!name || name.length > AI_PRODUCT_OPTION_NAME_MAX_CHARS || BRACKET.test(name)) {\n fail(findings, `${here}.name`, 'option-name', `An option name is a plain word or phrase of at most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters.`, record['name'])\n }\n const values = Array.isArray(record['values']) ? record['values'].map(line).filter(Boolean) : []\n if (!values.length || values.length > AI_CATALOG_OPTION_VALUES_MAX) {\n fail(findings, `${here}.values`, 'too-many', `An option has between 1 and ${AI_CATALOG_OPTION_VALUES_MAX} values.`, record['values'])\n }\n if (values.some((value) => value.length > AI_CATALOG_OPTION_VALUE_MAX_CHARS || BRACKET.test(value))) {\n fail(findings, `${here}.values`, 'option-values', `An option value is a plain word or phrase of at most ${AI_CATALOG_OPTION_VALUE_MAX_CHARS} characters.`, record['values'])\n }\n if (new Set(values.map((value) => value.toLowerCase())).size !== values.length) {\n fail(findings, `${here}.values`, 'option-values', 'An option’s values are all different.', record['values'])\n }\n options.push({ name, values: values.slice(0, AI_CATALOG_OPTION_VALUES_MAX) })\n })\n if (new Set(options.map((option) => option.name.toLowerCase())).size !== options.length) {\n fail(findings, at, 'option-duplicate', 'Each option needs its own name.', raw)\n }\n return options\n}\n\n/* ------------------------------------------------------------------------ *\n * Categories and discounts\n * ------------------------------------------------------------------------ */\n\nexport const AI_CATEGORIES_TOOL: AiTool = {\n name: AI_CATEGORIES_TOOL_NAME,\n description: 'Propose store categories and a first set of discounts, each created only if the owner accepts it. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['categories', 'discounts'],\n properties: {\n categories: {\n type: 'array',\n description: `At most ${AI_CATEGORIES_MAX} categories; empty when the brief gives no reason for any.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'why'],\n properties: {\n name: string(`At most ${AI_CATEGORY_NAME_MAX_CHARS} characters.`),\n why: string(`One sentence of at most ${AI_PROPOSAL_WHY_MAX_CHARS} characters.`),\n },\n },\n },\n discounts: {\n type: 'array',\n description: `At most ${AI_DISCOUNTS_MAX} discounts; empty when the brief gives no reason for any.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'code', 'kind', 'value', 'minimumOrderUsd', 'why'],\n properties: {\n name: string(`At most ${AI_CATEGORY_NAME_MAX_CHARS} characters.`),\n code: string('The code a shopper types: 3 to 20 capital letters, digits, dashes or underscores; empty for a discount that applies on its own.'),\n kind: { type: 'string', enum: ['percent', 'fixed', 'free_shipping'] },\n value: {\n type: 'number',\n description: `percent: the percentage, 1 to ${AI_DISCOUNT_MAX_PERCENT}; fixed: dollars off, at most ${AI_DISCOUNT_MAX_FIXED_USD}; free_shipping: 0.`,\n },\n minimumOrderUsd: { type: 'number', description: 'The smallest order it applies to, in dollars; 0 for any order.' },\n why: string(`One sentence of at most ${AI_PROPOSAL_WHY_MAX_CHARS} characters.`),\n },\n },\n },\n },\n },\n}\n\n/** A categories answer held to the catalog and discount editors and the storefront copy rules. */\nexport function checkAiCategories(\n answer: Record<string, unknown>,\n context: { existingCategoryNames: readonly string[]; merchantWords: string },\n): AiGenerationCheckResult<Pick<AiCategoriesProposal, 'categories' | 'discounts'>> {\n const findings: Findings = { violations: [], offending: {} }\n const existing = new Set(context.existingCategoryNames.map((name) => line(name).toLowerCase()))\n const samples: AiStorefrontCopySample[] = []\n\n const categories: AiCategoriesProposal['categories'] = []\n const rawCategories = answer['categories']\n if (!Array.isArray(rawCategories)) {\n fail(findings, 'categories', 'type', 'Categories are a list.', rawCategories)\n } else {\n if (rawCategories.length > AI_CATEGORIES_MAX) {\n fail(findings, 'categories', 'too-many', `Propose at most ${AI_CATEGORIES_MAX} categories.`, `${rawCategories.length} categories`)\n }\n const seen = new Set<string>()\n rawCategories.slice(0, AI_CATEGORIES_MAX).forEach((entry, index) => {\n const at = `categories[${index}]`\n const record = isRecord(entry) ? entry : {}\n const name = checkCopyText(findings, `${at}.name`, record['name'], {\n label: 'A category name',\n max: AI_CATEGORY_NAME_MAX_CHARS,\n brackets: false,\n })\n const key = name.toLowerCase()\n if (name && existing.has(key)) {\n fail(findings, `${at}.name`, 'category-exists', `The store already has a category named ${name}.`, record['name'])\n } else if (name && seen.has(key)) {\n fail(findings, `${at}.name`, 'category-duplicate', 'Each category needs its own name.', record['name'])\n }\n seen.add(key)\n const why = checkCopyText(findings, `${at}.why`, record['why'], {\n label: 'Why a category helps',\n max: AI_PROPOSAL_WHY_MAX_CHARS,\n brackets: false,\n })\n samples.push({ at: `${at}.name`, text: name }, { at: `${at}.why`, text: why })\n categories.push({ name, why })\n })\n }\n\n const discounts: AiProposedDiscount[] = []\n const rawDiscounts = answer['discounts']\n if (!Array.isArray(rawDiscounts)) {\n fail(findings, 'discounts', 'type', 'Discounts are a list.', rawDiscounts)\n } else {\n if (rawDiscounts.length > AI_DISCOUNTS_MAX) {\n fail(findings, 'discounts', 'too-many', `Propose at most ${AI_DISCOUNTS_MAX} discounts.`, `${rawDiscounts.length} discounts`)\n }\n const codes = new Set<string>()\n rawDiscounts.slice(0, AI_DISCOUNTS_MAX).forEach((entry, index) => {\n const at = `discounts[${index}]`\n const record = isRecord(entry) ? entry : {}\n const name = checkCopyText(findings, `${at}.name`, record['name'], {\n label: 'A discount name',\n max: AI_CATEGORY_NAME_MAX_CHARS,\n brackets: false,\n })\n const typedCode = line(record['code']).toUpperCase()\n if (typedCode && !AI_DISCOUNT_CODE.test(typedCode)) {\n fail(findings, `${at}.code`, 'code', 'A code is 3 to 20 capital letters, digits, dashes or underscores.', record['code'])\n } else if (typedCode && codes.has(typedCode)) {\n fail(findings, `${at}.code`, 'code-duplicate', 'Each discount needs its own code.', record['code'])\n }\n if (typedCode) codes.add(typedCode)\n const kind = record['kind']\n const value = Number(record['value'])\n const minimum = Number(record['minimumOrderUsd'])\n let valuePct: number | null = null\n let valueCents: number | null = null\n if (kind === 'percent') {\n if (!Number.isInteger(value) || value < 1 || value > AI_DISCOUNT_MAX_PERCENT) {\n fail(findings, `${at}.value`, 'discount-value', `A percentage is a whole number from 1 to ${AI_DISCOUNT_MAX_PERCENT}.`, record['value'])\n }\n valuePct = value\n } else if (kind === 'fixed') {\n if (!Number.isFinite(value) || value <= 0 || value > AI_DISCOUNT_MAX_FIXED_USD) {\n fail(findings, `${at}.value`, 'discount-value', `An amount off is more than 0 and at most ${AI_DISCOUNT_MAX_FIXED_USD} dollars.`, record['value'])\n }\n valueCents = Math.round(value * 100)\n } else if (kind !== 'free_shipping') {\n fail(findings, `${at}.kind`, 'type', 'A discount is a percentage, an amount off or free shipping.', kind)\n }\n if (!Number.isFinite(minimum) || minimum < 0 || minimum > 10_000) {\n fail(findings, `${at}.minimumOrderUsd`, 'discount-minimum', 'A minimum order is between 0 and 10,000 dollars.', record['minimumOrderUsd'])\n }\n const why = checkCopyText(findings, `${at}.why`, record['why'], {\n label: 'Why a discount helps',\n max: AI_PROPOSAL_WHY_MAX_CHARS,\n brackets: false,\n })\n // The discount's own amount is stated in its fields, so only its name\n // and reason are read for a claim: \"15% off\" is the discount, not a price.\n samples.push({ at: `${at}.name`, text: name }, { at: `${at}.why`, text: why })\n discounts.push({\n name,\n code: typedCode || null,\n kind: kind as AiDiscountKind,\n valuePct,\n valueCents,\n minSubtotalCents: minimum > 0 ? Math.round(minimum * 100) : null,\n why,\n })\n })\n }\n\n if (!categories.length && !discounts.length && !findings.violations.length) {\n fail(findings, 'categories', 'empty', 'Propose at least one category or one discount.', answer)\n }\n findings.violations.push(\n ...storefrontClaimViolations(findStorefrontClaims(samples, context.merchantWords)),\n ...detectOffVoiceCopy(samples, null),\n )\n return {\n value: findings.violations.length ? null : { categories, discounts },\n violations: findings.violations,\n offending: findings.offending,\n }\n}\n"],"names":["SEO_LISTING_FIELDS","AI_CATALOG_DESCRIPTION_MAX_CHARS","AI_CATALOG_OPTION_VALUE_MAX_CHARS","AI_CATALOG_OPTION_VALUES_MAX","AI_CATALOG_OPTIONS_MAX","AI_CATALOG_PHOTO_MAX_CHARS","AI_CATALOG_PRODUCTS","AI_CATALOG_TAGS_MAX","AI_CATEGORIES_MAX","AI_CATEGORY_NAME_MAX_CHARS","AI_DISCOUNT_CODE","AI_DISCOUNT_MAX_FIXED_USD","AI_DISCOUNT_MAX_PERCENT","AI_DISCOUNTS_MAX","AI_PRODUCT_CATEGORIES_MAX","AI_PRODUCT_DESCRIPTION_MAX_CHARS","AI_PRODUCT_NAME_MAX_CHARS","AI_PRODUCT_OPTION_NAME_MAX_CHARS","AI_PRODUCT_TAG_MAX_CHARS","AI_PRODUCT_TAGS_MAX","AI_PRODUCT_TYPES","AI_PROPOSAL_WHY_MAX_CHARS","findInventedPrices","findStorefrontClaims","storefrontClaimViolations","detectOffVoiceCopy","AI_PRODUCT_COPY_TOOL_NAME","AI_CATALOG_TOOL_NAME","AI_CATEGORIES_TOOL_NAME","SEO_TITLE_MAX","title","maxLength","SEO_DESCRIPTION_MAX","description","string","type","strings","items","isRecord","value","Array","isArray","line","replace","trim","paragraphs","split","map","part","join","MARKUP","MARKDOWN","EMOJI","BRACKET","URL","fail","findings","at","code","message","violations","push","rule","paths","offending","checkCopyText","raw","bounds","label","multiline","length","max","test","brackets","checkTags","seen","Set","tags","forEach","entry","index","tag","key","toLowerCase","has","add","claimViolations","samples","merchantWords","AI_PRODUCT_COPY_TOOL","name","strict","inputSchema","additionalProperties","required","properties","seoTitle","seoDescription","categoryIds","optionNames","checkAiProductCopy","answer","context","allowed","rawCategories","id","includes","rawOptions","optionCount","slice","size","text","AI_CATALOG_TOOL","products","min","enum","options","values","photo","checkAiCatalog","names","checkCatalogOptions","tagIndex","here","record","filter","Boolean","some","option","AI_CATEGORIES_TOOL","categories","why","discounts","kind","minimumOrderUsd","checkAiCategories","existing","existingCategoryNames","rawDiscounts","codes","typedCode","toUpperCase","Number","minimum","valuePct","valueCents","isInteger","isFinite","Math","round","minSubtotalCents"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-products-tool.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 { SEO_LISTING_FIELDS } from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport {\n AI_CATALOG_DESCRIPTION_MAX_CHARS,\n AI_CATALOG_OPTION_VALUE_MAX_CHARS,\n AI_CATALOG_OPTION_VALUES_MAX,\n AI_CATALOG_OPTIONS_MAX,\n AI_CATALOG_PHOTO_MAX_CHARS,\n AI_CATALOG_PRICE_USD,\n AI_CATALOG_PRODUCTS,\n AI_CATALOG_TAGS_MAX,\n AI_CATEGORIES_MAX,\n AI_CATEGORY_NAME_MAX_CHARS,\n AI_DISCOUNT_CODE,\n AI_DISCOUNT_MAX_FIXED_USD,\n AI_DISCOUNT_MAX_PERCENT,\n AI_DISCOUNTS_MAX,\n AI_PRODUCT_CATEGORIES_MAX,\n AI_PRODUCT_DESCRIPTION_MAX_CHARS,\n AI_PRODUCT_NAME_MAX_CHARS,\n AI_PRODUCT_OPTION_NAME_MAX_CHARS,\n AI_PRODUCT_TAG_MAX_CHARS,\n AI_PRODUCT_TAGS_MAX,\n AI_PRODUCT_TYPES,\n AI_PROPOSAL_WHY_MAX_CHARS,\n aiCatalogPrice,\n type AiCatalogProduct,\n type AiCategoriesProposal,\n type AiDiscountKind,\n type AiProductCopy,\n type AiProductOption,\n type AiProductType,\n type AiProposedDiscount,\n} from '../model/ai-products'\nimport {\n findInventedPrices,\n findStorefrontClaims,\n storefrontClaimViolations,\n type AiStorefrontCopySample,\n} from '../model/ai-storefront-claims'\nimport type { AiTool } from '../providers/contract'\nimport type { AiGenerationCheckResult } from '../runtime/ai-doctrine'\nimport { detectOffVoiceCopy, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\n\n/**\n * The strict tools commerce by AI answers through (AGL-2916), and the checks\n * that hold each answer to the commerce editor it proposes for.\n *\n * - `propose_product_copy` — one product's description, search listing, tags,\n * categories and option names.\n * - `propose_catalog` — a store's first products, from a brief.\n * - `propose_categories_and_discounts` — store categories and a first set of\n * discounts, from a brief.\n *\n * Each schema is kept small, and no field is nullable: \"none\" is an empty\n * string, an empty list or zero. A catalog product's `price` is the one field\n * no answer is refused for: code keeps it in range or drops it to the\n * store's default (`aiCatalogPrice`). A strict schema accepts no length keyword,\n * so every bound is stated in a description and enforced here. A check\n * returns a value only when nothing in the answer is broken, and otherwise\n * the violations and the parts at fault, for the generation call's one\n * re-ask.\n */\n\nexport const AI_PRODUCT_COPY_TOOL_NAME = 'propose_product_copy'\nexport const AI_CATALOG_TOOL_NAME = 'propose_catalog'\nexport const AI_CATEGORIES_TOOL_NAME = 'propose_categories_and_discounts'\n\nconst SEO_TITLE_MAX = SEO_LISTING_FIELDS.title.maxLength\nconst SEO_DESCRIPTION_MAX = SEO_LISTING_FIELDS.description.maxLength\n\nconst string = (description: string) => ({ type: 'string', description })\nconst strings = (description: string) => ({ type: 'array', items: { type: 'string' }, description })\n\n/* ------------------------------------------------------------------------ *\n * Shared reads\n * ------------------------------------------------------------------------ */\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** One line: runs of white space folded to one space. */\nconst line = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\n\n/** Paragraphs: spaces folded within a line, at most one blank line between paragraphs. */\nconst paragraphs = (value: unknown): string =>\n typeof value === 'string'\n ? value\n .replace(/\\r\\n?/g, '\\n')\n .split('\\n')\n .map((part) => part.replace(/[ \\t\\f\\v]+/g, ' ').trim())\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n : ''\n\nconst MARKUP = /<\\/?[a-z][^>]*>/i\nconst MARKDOWN = /(^|\\n)\\s*#{1,6}\\s|\\*\\*[^*\\n]+\\*\\*|__[^_\\n]+__|!\\[[^\\]]*\\]\\(|\\[[^\\]]+\\]\\(https?:/\nconst EMOJI = /\\p{Extended_Pictographic}/u\nconst BRACKET = /[[\\]]/\nconst URL = /\\bhttps?:\\/\\/|\\bwww\\./i\n\ninterface Findings {\n violations: AiDoctrineViolation[]\n offending: Record<string, unknown>\n}\n\nfunction fail(findings: Findings, at: string, code: string, message: string, value: unknown): void {\n findings.violations.push({ rule: null, code, message, paths: [at] })\n if (!(at in findings.offending)) findings.offending[at] = value\n}\n\n/** A piece of copy a shopper reads: there, within its length, and plain text. */\nfunction checkCopyText(\n findings: Findings,\n at: string,\n raw: unknown,\n bounds: { label: string; max: number; multiline?: boolean; brackets?: boolean },\n): string {\n if (typeof raw !== 'string') {\n fail(findings, at, 'type', `${bounds.label} must be text.`, raw)\n return ''\n }\n const value = bounds.multiline ? paragraphs(raw) : line(raw)\n if (!value) {\n fail(findings, at, 'missing', `${bounds.label} is required.`, raw)\n return ''\n }\n if (value.length > bounds.max) {\n fail(findings, at, 'too-long', `${bounds.label} is ${value.length} characters; it may have ${bounds.max}.`, raw)\n }\n if (MARKUP.test(value) || MARKDOWN.test(value)) {\n fail(findings, at, 'markup', `${bounds.label} is plain text, with no markup or formatting marks.`, raw)\n }\n if (EMOJI.test(value)) fail(findings, at, 'emoji', `${bounds.label} holds no emoji.`, raw)\n if (URL.test(value)) fail(findings, at, 'link', `${bounds.label} holds no link.`, raw)\n if (bounds.brackets === false && BRACKET.test(value)) {\n fail(findings, at, 'bracket', `${bounds.label} is published as written, so it marks no missing fact in brackets.`, raw)\n }\n return value\n}\n\n/** Tags a shopper filters by: short, distinct, no `#`, no more than `max`. */\nfunction checkTags(findings: Findings, at: string, raw: unknown, max = AI_PRODUCT_TAGS_MAX): string[] {\n if (!Array.isArray(raw)) {\n fail(findings, at, 'type', 'Tags are a list of words or short phrases.', raw)\n return []\n }\n const seen = new Set<string>()\n const tags: string[] = []\n raw.forEach((entry, index) => {\n const tag = line(entry).replace(/^#+/, '').trim()\n if (!tag) return\n if (tag.length > AI_PRODUCT_TAG_MAX_CHARS || EMOJI.test(tag) || BRACKET.test(tag)) {\n fail(findings, `${at}[${index}]`, 'tag', `A tag is a plain word or phrase of at most ${AI_PRODUCT_TAG_MAX_CHARS} characters.`, entry)\n return\n }\n const key = tag.toLowerCase()\n if (seen.has(key)) return\n seen.add(key)\n tags.push(tag)\n })\n if (tags.length > max) {\n fail(findings, at, 'too-many', `A product carries at most ${max} tags.`, raw)\n }\n return tags\n}\n\n/** The claims and prices a set of copy may not make, as violations. */\nfunction claimViolations(samples: readonly AiStorefrontCopySample[], merchantWords: string): AiDoctrineViolation[] {\n return storefrontClaimViolations(\n findStorefrontClaims(samples, merchantWords),\n findInventedPrices(samples, merchantWords),\n )\n}\n\n/* ------------------------------------------------------------------------ *\n * One product's copy\n * ------------------------------------------------------------------------ */\n\nexport const AI_PRODUCT_COPY_TOOL: AiTool = {\n name: AI_PRODUCT_COPY_TOOL_NAME,\n description: 'Propose the storefront copy for one product. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['description', 'seoTitle', 'seoDescription', 'tags', 'categoryIds', 'optionNames'],\n properties: {\n description: string(\n `The product description shoppers read, in short paragraphs, at most ${AI_PRODUCT_DESCRIPTION_MAX_CHARS} characters.`,\n ),\n seoTitle: string(`The search result headline, published as written, at most ${SEO_TITLE_MAX} characters.`),\n seoDescription: string(`The search result summary, at most ${SEO_DESCRIPTION_MAX} characters.`),\n tags: strings(`At most ${AI_PRODUCT_TAGS_MAX} tags, each at most ${AI_PRODUCT_TAG_MAX_CHARS} characters.`),\n categoryIds: strings(\n `At most ${AI_PRODUCT_CATEGORIES_MAX} category ids from the list you were given; empty when none fits.`,\n ),\n optionNames: strings(\n `One name per option the product has, in order, each at most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters; empty when it has none.`,\n ),\n },\n },\n}\n\nexport interface AiProductCopyCheckContext {\n /** The ids of the site's categories the request listed. */\n categoryIds: readonly string[]\n /** How many options the product has. */\n optionCount: number\n /** The merchant's own words about the product: its name, text and tags. */\n merchantWords: string\n}\n\n/**\n * A product copy answer held to the editor: every field written and within\n * its length, plain text, the categories the request listed, one distinct\n * name per option, and no claim or price the copy may not make.\n */\nexport function checkAiProductCopy(\n answer: Record<string, unknown>,\n context: AiProductCopyCheckContext,\n): AiGenerationCheckResult<AiProductCopy> {\n const findings: Findings = { violations: [], offending: {} }\n const description = checkCopyText(findings, 'description', answer['description'], {\n label: 'The description',\n max: AI_PRODUCT_DESCRIPTION_MAX_CHARS,\n multiline: true,\n })\n const seoTitle = checkCopyText(findings, 'seoTitle', answer['seoTitle'], {\n label: 'The search title',\n max: SEO_TITLE_MAX,\n brackets: false,\n })\n const seoDescription = checkCopyText(findings, 'seoDescription', answer['seoDescription'], {\n label: 'The search description',\n max: SEO_DESCRIPTION_MAX,\n brackets: false,\n })\n const tags = checkTags(findings, 'tags', answer['tags'])\n\n const allowed = new Set(context.categoryIds)\n const categoryIds: string[] = []\n const rawCategories = answer['categoryIds']\n if (!Array.isArray(rawCategories)) {\n fail(findings, 'categoryIds', 'type', 'Categories are a list of ids.', rawCategories)\n } else {\n for (const entry of rawCategories) {\n const id = line(entry)\n if (!id || categoryIds.includes(id)) continue\n if (!allowed.has(id)) {\n fail(findings, 'categoryIds', 'category-unknown', 'Use only the category ids you were given.', rawCategories)\n continue\n }\n categoryIds.push(id)\n }\n if (categoryIds.length > AI_PRODUCT_CATEGORIES_MAX) {\n fail(findings, 'categoryIds', 'too-many', `A product goes in at most ${AI_PRODUCT_CATEGORIES_MAX} categories.`, rawCategories)\n }\n }\n\n const optionNames: string[] = []\n const rawOptions = answer['optionNames']\n if (!Array.isArray(rawOptions)) {\n fail(findings, 'optionNames', 'type', 'Option names are a list.', rawOptions)\n } else {\n if (rawOptions.length !== context.optionCount) {\n fail(\n findings,\n 'optionNames',\n 'option-count',\n `The product has ${context.optionCount} ${context.optionCount === 1 ? 'option' : 'options'}; give one name for each.`,\n rawOptions,\n )\n }\n rawOptions.slice(0, context.optionCount).forEach((entry, index) => {\n const name = line(entry)\n if (!name || name.length > AI_PRODUCT_OPTION_NAME_MAX_CHARS || BRACKET.test(name) || EMOJI.test(name)) {\n fail(findings, `optionNames[${index}]`, 'option-name', `An option name is a plain word or phrase of at most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters.`, entry)\n }\n optionNames.push(name)\n })\n if (new Set(optionNames.map((name) => name.toLowerCase())).size !== optionNames.length) {\n fail(findings, 'optionNames', 'option-duplicate', 'Each option needs its own name.', rawOptions)\n }\n }\n\n const samples: AiStorefrontCopySample[] = [\n { at: 'description', text: description },\n { at: 'seoTitle', text: seoTitle },\n { at: 'seoDescription', text: seoDescription },\n ...tags.map((tag, index) => ({ at: `tags[${index}]`, text: tag })),\n ]\n findings.violations.push(\n ...claimViolations(samples, context.merchantWords),\n ...detectOffVoiceCopy(samples, null),\n )\n const value = findings.violations.length\n ? null\n : { description, seoTitle, seoDescription, tags, categoryIds, optionNames }\n return { value, violations: findings.violations, offending: findings.offending }\n}\n\n/* ------------------------------------------------------------------------ *\n * A store's first products\n * ------------------------------------------------------------------------ */\n\nexport const AI_CATALOG_TOOL: AiTool = {\n name: AI_CATALOG_TOOL_NAME,\n description: 'Propose the products of a new store, each a draft its owner completes. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['products'],\n properties: {\n products: {\n type: 'array',\n description: `Between ${AI_CATALOG_PRODUCTS.min} and ${AI_CATALOG_PRODUCTS.max} products.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'type', 'description', 'tags', 'options', 'seoTitle', 'seoDescription', 'photo', 'price'],\n properties: {\n name: string(`Distinct within the list, at most ${AI_PRODUCT_NAME_MAX_CHARS} characters.`),\n type: { type: 'string', enum: [...AI_PRODUCT_TYPES] },\n description: string(`In short paragraphs, at most ${AI_CATALOG_DESCRIPTION_MAX_CHARS} characters.`),\n tags: strings(`At most ${AI_CATALOG_TAGS_MAX} tags, each at most ${AI_PRODUCT_TAG_MAX_CHARS} characters.`),\n options: {\n type: 'array',\n description: `The choices a shopper makes, at most ${AI_CATALOG_OPTIONS_MAX}; empty for a product with none.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'values'],\n properties: {\n name: string(`At most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters.`),\n values: strings(\n `At most ${AI_CATALOG_OPTION_VALUES_MAX} distinct values, each at most ${AI_CATALOG_OPTION_VALUE_MAX_CHARS} characters.`,\n ),\n },\n },\n },\n seoTitle: string(`At most ${SEO_TITLE_MAX} characters.`),\n seoDescription: string(`At most ${SEO_DESCRIPTION_MAX} characters.`),\n photo: string(`What the product photo should show, in one sentence of at most ${AI_CATALOG_PHOTO_MAX_CHARS} characters.`),\n price: {\n type: 'number',\n description: `Its suggested price in US dollars, from ${AI_CATALOG_PRICE_USD.min} to ${AI_CATALOG_PRICE_USD.max}.`,\n },\n },\n },\n },\n },\n },\n}\n\n/** A catalog answer held to the products editor and the storefront copy rules. */\nexport function checkAiCatalog(\n answer: Record<string, unknown>,\n context: { merchantWords: string },\n): AiGenerationCheckResult<AiCatalogProduct[]> {\n const findings: Findings = { violations: [], offending: {} }\n const raw = answer['products']\n if (!Array.isArray(raw)) {\n fail(findings, 'products', 'type', 'The products are a list.', raw)\n return { value: null, violations: findings.violations, offending: findings.offending }\n }\n if (raw.length < AI_CATALOG_PRODUCTS.min || raw.length > AI_CATALOG_PRODUCTS.max) {\n fail(findings, 'products', 'product-count', `Propose between ${AI_CATALOG_PRODUCTS.min} and ${AI_CATALOG_PRODUCTS.max} products.`, `${raw.length} products`)\n }\n const names = new Set<string>()\n const products: AiCatalogProduct[] = []\n const samples: AiStorefrontCopySample[] = []\n raw.slice(0, AI_CATALOG_PRODUCTS.max).forEach((entry, index) => {\n const at = `products[${index}]`\n if (!isRecord(entry)) {\n fail(findings, at, 'type', 'Each product is an object.', entry)\n return\n }\n const name = checkCopyText(findings, `${at}.name`, entry['name'], {\n label: 'A product name',\n max: AI_PRODUCT_NAME_MAX_CHARS,\n brackets: false,\n })\n if (name && names.has(name.toLowerCase())) {\n fail(findings, `${at}.name`, 'name-duplicate', 'Each product needs its own name.', entry['name'])\n }\n names.add(name.toLowerCase())\n const type = entry['type']\n if (!(AI_PRODUCT_TYPES as readonly unknown[]).includes(type)) {\n fail(findings, `${at}.type`, 'type', 'A product is physical, digital or a service.', type)\n }\n const description = checkCopyText(findings, `${at}.description`, entry['description'], {\n label: 'A product description',\n max: AI_CATALOG_DESCRIPTION_MAX_CHARS,\n multiline: true,\n })\n const tags = checkTags(findings, `${at}.tags`, entry['tags'], AI_CATALOG_TAGS_MAX)\n const options = checkCatalogOptions(findings, `${at}.options`, entry['options'])\n const seoTitle = checkCopyText(findings, `${at}.seoTitle`, entry['seoTitle'], {\n label: 'A search title',\n max: SEO_TITLE_MAX,\n brackets: false,\n })\n const seoDescription = checkCopyText(findings, `${at}.seoDescription`, entry['seoDescription'], {\n label: 'A search description',\n max: SEO_DESCRIPTION_MAX,\n brackets: false,\n })\n const photo = checkCopyText(findings, `${at}.photo`, entry['photo'], {\n label: 'What the photo shows',\n max: AI_CATALOG_PHOTO_MAX_CHARS,\n brackets: false,\n })\n samples.push(\n { at: `${at}.name`, text: name },\n { at: `${at}.description`, text: description },\n { at: `${at}.seoTitle`, text: seoTitle },\n { at: `${at}.seoDescription`, text: seoDescription },\n ...tags.map((tag, tagIndex) => ({ at: `${at}.tags[${tagIndex}]`, text: tag })),\n )\n products.push({\n name,\n type: type as AiProductType,\n description,\n tags,\n options,\n seoTitle,\n seoDescription,\n photo,\n // Kept by code, never re-asked (Zach, 2026-10-12): a price that is\n // missing or out of range is `null`, and the store's default stands in.\n price: aiCatalogPrice(entry['price']),\n })\n })\n findings.violations.push(...claimViolations(samples, context.merchantWords), ...detectOffVoiceCopy(samples, null))\n return {\n value: findings.violations.length ? null : products,\n violations: findings.violations,\n offending: findings.offending,\n }\n}\n\nfunction checkCatalogOptions(findings: Findings, at: string, raw: unknown): AiProductOption[] {\n if (!Array.isArray(raw)) {\n fail(findings, at, 'type', 'Options are a list.', raw)\n return []\n }\n if (raw.length > AI_CATALOG_OPTIONS_MAX) {\n fail(findings, at, 'too-many', `A proposed product has at most ${AI_CATALOG_OPTIONS_MAX} options.`, raw)\n }\n const options: AiProductOption[] = []\n raw.slice(0, AI_CATALOG_OPTIONS_MAX).forEach((entry, index) => {\n const here = `${at}[${index}]`\n const record = isRecord(entry) ? entry : {}\n const name = line(record['name'])\n if (!name || name.length > AI_PRODUCT_OPTION_NAME_MAX_CHARS || BRACKET.test(name)) {\n fail(findings, `${here}.name`, 'option-name', `An option name is a plain word or phrase of at most ${AI_PRODUCT_OPTION_NAME_MAX_CHARS} characters.`, record['name'])\n }\n const values = Array.isArray(record['values']) ? record['values'].map(line).filter(Boolean) : []\n if (!values.length || values.length > AI_CATALOG_OPTION_VALUES_MAX) {\n fail(findings, `${here}.values`, 'too-many', `An option has between 1 and ${AI_CATALOG_OPTION_VALUES_MAX} values.`, record['values'])\n }\n if (values.some((value) => value.length > AI_CATALOG_OPTION_VALUE_MAX_CHARS || BRACKET.test(value))) {\n fail(findings, `${here}.values`, 'option-values', `An option value is a plain word or phrase of at most ${AI_CATALOG_OPTION_VALUE_MAX_CHARS} characters.`, record['values'])\n }\n if (new Set(values.map((value) => value.toLowerCase())).size !== values.length) {\n fail(findings, `${here}.values`, 'option-values', 'An option’s values are all different.', record['values'])\n }\n options.push({ name, values: values.slice(0, AI_CATALOG_OPTION_VALUES_MAX) })\n })\n if (new Set(options.map((option) => option.name.toLowerCase())).size !== options.length) {\n fail(findings, at, 'option-duplicate', 'Each option needs its own name.', raw)\n }\n return options\n}\n\n/* ------------------------------------------------------------------------ *\n * Categories and discounts\n * ------------------------------------------------------------------------ */\n\nexport const AI_CATEGORIES_TOOL: AiTool = {\n name: AI_CATEGORIES_TOOL_NAME,\n description: 'Propose store categories and a first set of discounts, each created only if the owner accepts it. Every field is required.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['categories', 'discounts'],\n properties: {\n categories: {\n type: 'array',\n description: `At most ${AI_CATEGORIES_MAX} categories; empty when the brief gives no reason for any.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'why'],\n properties: {\n name: string(`At most ${AI_CATEGORY_NAME_MAX_CHARS} characters.`),\n why: string(`One sentence of at most ${AI_PROPOSAL_WHY_MAX_CHARS} characters.`),\n },\n },\n },\n discounts: {\n type: 'array',\n description: `At most ${AI_DISCOUNTS_MAX} discounts; empty when the brief gives no reason for any.`,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'code', 'kind', 'value', 'minimumOrderUsd', 'why'],\n properties: {\n name: string(`At most ${AI_CATEGORY_NAME_MAX_CHARS} characters.`),\n code: string('The code a shopper types: 3 to 20 capital letters, digits, dashes or underscores; empty for a discount that applies on its own.'),\n kind: { type: 'string', enum: ['percent', 'fixed', 'free_shipping'] },\n value: {\n type: 'number',\n description: `percent: the percentage, 1 to ${AI_DISCOUNT_MAX_PERCENT}; fixed: dollars off, at most ${AI_DISCOUNT_MAX_FIXED_USD}; free_shipping: 0.`,\n },\n minimumOrderUsd: { type: 'number', description: 'The smallest order it applies to, in dollars; 0 for any order.' },\n why: string(`One sentence of at most ${AI_PROPOSAL_WHY_MAX_CHARS} characters.`),\n },\n },\n },\n },\n },\n}\n\n/** A categories answer held to the catalog and discount editors and the storefront copy rules. */\nexport function checkAiCategories(\n answer: Record<string, unknown>,\n context: { existingCategoryNames: readonly string[]; merchantWords: string },\n): AiGenerationCheckResult<Pick<AiCategoriesProposal, 'categories' | 'discounts'>> {\n const findings: Findings = { violations: [], offending: {} }\n const existing = new Set(context.existingCategoryNames.map((name) => line(name).toLowerCase()))\n const samples: AiStorefrontCopySample[] = []\n\n const categories: AiCategoriesProposal['categories'] = []\n const rawCategories = answer['categories']\n if (!Array.isArray(rawCategories)) {\n fail(findings, 'categories', 'type', 'Categories are a list.', rawCategories)\n } else {\n if (rawCategories.length > AI_CATEGORIES_MAX) {\n fail(findings, 'categories', 'too-many', `Propose at most ${AI_CATEGORIES_MAX} categories.`, `${rawCategories.length} categories`)\n }\n const seen = new Set<string>()\n rawCategories.slice(0, AI_CATEGORIES_MAX).forEach((entry, index) => {\n const at = `categories[${index}]`\n const record = isRecord(entry) ? entry : {}\n const name = checkCopyText(findings, `${at}.name`, record['name'], {\n label: 'A category name',\n max: AI_CATEGORY_NAME_MAX_CHARS,\n brackets: false,\n })\n const key = name.toLowerCase()\n if (name && existing.has(key)) {\n fail(findings, `${at}.name`, 'category-exists', `The store already has a category named ${name}.`, record['name'])\n } else if (name && seen.has(key)) {\n fail(findings, `${at}.name`, 'category-duplicate', 'Each category needs its own name.', record['name'])\n }\n seen.add(key)\n const why = checkCopyText(findings, `${at}.why`, record['why'], {\n label: 'Why a category helps',\n max: AI_PROPOSAL_WHY_MAX_CHARS,\n brackets: false,\n })\n samples.push({ at: `${at}.name`, text: name }, { at: `${at}.why`, text: why })\n categories.push({ name, why })\n })\n }\n\n const discounts: AiProposedDiscount[] = []\n const rawDiscounts = answer['discounts']\n if (!Array.isArray(rawDiscounts)) {\n fail(findings, 'discounts', 'type', 'Discounts are a list.', rawDiscounts)\n } else {\n if (rawDiscounts.length > AI_DISCOUNTS_MAX) {\n fail(findings, 'discounts', 'too-many', `Propose at most ${AI_DISCOUNTS_MAX} discounts.`, `${rawDiscounts.length} discounts`)\n }\n const codes = new Set<string>()\n rawDiscounts.slice(0, AI_DISCOUNTS_MAX).forEach((entry, index) => {\n const at = `discounts[${index}]`\n const record = isRecord(entry) ? entry : {}\n const name = checkCopyText(findings, `${at}.name`, record['name'], {\n label: 'A discount name',\n max: AI_CATEGORY_NAME_MAX_CHARS,\n brackets: false,\n })\n const typedCode = line(record['code']).toUpperCase()\n if (typedCode && !AI_DISCOUNT_CODE.test(typedCode)) {\n fail(findings, `${at}.code`, 'code', 'A code is 3 to 20 capital letters, digits, dashes or underscores.', record['code'])\n } else if (typedCode && codes.has(typedCode)) {\n fail(findings, `${at}.code`, 'code-duplicate', 'Each discount needs its own code.', record['code'])\n }\n if (typedCode) codes.add(typedCode)\n const kind = record['kind']\n const value = Number(record['value'])\n const minimum = Number(record['minimumOrderUsd'])\n let valuePct: number | null = null\n let valueCents: number | null = null\n if (kind === 'percent') {\n if (!Number.isInteger(value) || value < 1 || value > AI_DISCOUNT_MAX_PERCENT) {\n fail(findings, `${at}.value`, 'discount-value', `A percentage is a whole number from 1 to ${AI_DISCOUNT_MAX_PERCENT}.`, record['value'])\n }\n valuePct = value\n } else if (kind === 'fixed') {\n if (!Number.isFinite(value) || value <= 0 || value > AI_DISCOUNT_MAX_FIXED_USD) {\n fail(findings, `${at}.value`, 'discount-value', `An amount off is more than 0 and at most ${AI_DISCOUNT_MAX_FIXED_USD} dollars.`, record['value'])\n }\n valueCents = Math.round(value * 100)\n } else if (kind !== 'free_shipping') {\n fail(findings, `${at}.kind`, 'type', 'A discount is a percentage, an amount off or free shipping.', kind)\n }\n if (!Number.isFinite(minimum) || minimum < 0 || minimum > 10_000) {\n fail(findings, `${at}.minimumOrderUsd`, 'discount-minimum', 'A minimum order is between 0 and 10,000 dollars.', record['minimumOrderUsd'])\n }\n const why = checkCopyText(findings, `${at}.why`, record['why'], {\n label: 'Why a discount helps',\n max: AI_PROPOSAL_WHY_MAX_CHARS,\n brackets: false,\n })\n // The discount's own amount is stated in its fields, so only its name\n // and reason are read for a claim: \"15% off\" is the discount, not a price.\n samples.push({ at: `${at}.name`, text: name }, { at: `${at}.why`, text: why })\n discounts.push({\n name,\n code: typedCode || null,\n kind: kind as AiDiscountKind,\n valuePct,\n valueCents,\n minSubtotalCents: minimum > 0 ? Math.round(minimum * 100) : null,\n why,\n })\n })\n }\n\n if (!categories.length && !discounts.length && !findings.violations.length) {\n fail(findings, 'categories', 'empty', 'Propose at least one category or one discount.', answer)\n }\n findings.violations.push(\n ...storefrontClaimViolations(findStorefrontClaims(samples, context.merchantWords)),\n ...detectOffVoiceCopy(samples, null),\n )\n return {\n value: findings.violations.length ? null : { categories, discounts },\n violations: findings.violations,\n offending: findings.offending,\n }\n}\n"],"names":["SEO_LISTING_FIELDS","AI_CATALOG_DESCRIPTION_MAX_CHARS","AI_CATALOG_OPTION_VALUE_MAX_CHARS","AI_CATALOG_OPTION_VALUES_MAX","AI_CATALOG_OPTIONS_MAX","AI_CATALOG_PHOTO_MAX_CHARS","AI_CATALOG_PRICE_USD","AI_CATALOG_PRODUCTS","AI_CATALOG_TAGS_MAX","AI_CATEGORIES_MAX","AI_CATEGORY_NAME_MAX_CHARS","AI_DISCOUNT_CODE","AI_DISCOUNT_MAX_FIXED_USD","AI_DISCOUNT_MAX_PERCENT","AI_DISCOUNTS_MAX","AI_PRODUCT_CATEGORIES_MAX","AI_PRODUCT_DESCRIPTION_MAX_CHARS","AI_PRODUCT_NAME_MAX_CHARS","AI_PRODUCT_OPTION_NAME_MAX_CHARS","AI_PRODUCT_TAG_MAX_CHARS","AI_PRODUCT_TAGS_MAX","AI_PRODUCT_TYPES","AI_PROPOSAL_WHY_MAX_CHARS","aiCatalogPrice","findInventedPrices","findStorefrontClaims","storefrontClaimViolations","detectOffVoiceCopy","AI_PRODUCT_COPY_TOOL_NAME","AI_CATALOG_TOOL_NAME","AI_CATEGORIES_TOOL_NAME","SEO_TITLE_MAX","title","maxLength","SEO_DESCRIPTION_MAX","description","string","type","strings","items","isRecord","value","Array","isArray","line","replace","trim","paragraphs","split","map","part","join","MARKUP","MARKDOWN","EMOJI","BRACKET","URL","fail","findings","at","code","message","violations","push","rule","paths","offending","checkCopyText","raw","bounds","label","multiline","length","max","test","brackets","checkTags","seen","Set","tags","forEach","entry","index","tag","key","toLowerCase","has","add","claimViolations","samples","merchantWords","AI_PRODUCT_COPY_TOOL","name","strict","inputSchema","additionalProperties","required","properties","seoTitle","seoDescription","categoryIds","optionNames","checkAiProductCopy","answer","context","allowed","rawCategories","id","includes","rawOptions","optionCount","slice","size","text","AI_CATALOG_TOOL","products","min","enum","options","values","photo","price","checkAiCatalog","names","checkCatalogOptions","tagIndex","here","record","filter","Boolean","some","option","AI_CATEGORIES_TOOL","categories","why","discounts","kind","minimumOrderUsd","checkAiCategories","existing","existingCategoryNames","rawDiscounts","codes","typedCode","toUpperCase","Number","minimum","valuePct","valueCents","isInteger","isFinite","Math","round","minSubtotalCents"],"mappings":"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"}