@aglyn/plugins-ai 1.0.0-beta.236 → 1.0.0-beta.238

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 (164) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +44 -0
  3. package/src/lib/components/ai-credits-prompt.component.js +117 -0
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -0
  5. package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
  6. package/src/lib/components/ai-job-cancel.component.js +156 -0
  7. package/src/lib/components/ai-job-cancel.component.js.map +1 -0
  8. package/src/lib/components/ai-job-plan.component.d.ts +5 -4
  9. package/src/lib/components/ai-job-plan.component.js +48 -12
  10. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  11. package/src/lib/components/ai-job-requests.d.ts +10 -0
  12. package/src/lib/components/ai-job-requests.js +12 -4
  13. package/src/lib/components/ai-job-requests.js.map +1 -1
  14. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  15. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  16. package/src/lib/components/ai-site-build-page.component.js +48 -9
  17. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  18. package/src/lib/components/ai-site-start-card.component.js +68 -22
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  21. package/src/lib/components/ai-usage-strip.component.js +40 -9
  22. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  23. package/src/lib/components/assist-build-card.component.d.ts +3 -1
  24. package/src/lib/components/assist-build-card.component.js +58 -7
  25. package/src/lib/components/assist-build-card.component.js.map +1 -1
  26. package/src/lib/components/assist-jobs-drawer.component.js +66 -22
  27. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  28. package/src/lib/components/assist-panel.component.js +89 -52
  29. package/src/lib/components/assist-panel.component.js.map +1 -1
  30. package/src/lib/jobs/ai-job-admission.d.ts +22 -2
  31. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  32. package/src/lib/jobs/ai-job-build-step.d.ts +24 -11
  33. package/src/lib/jobs/ai-job-build-step.js +54 -35
  34. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  36. package/src/lib/jobs/ai-job-cancel.js +102 -0
  37. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  38. package/src/lib/jobs/ai-job-component-step.js +4 -1
  39. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-drafts.js +2 -0
  41. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  42. package/src/lib/jobs/ai-job-form-step.js +4 -1
  43. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  45. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  46. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  47. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  48. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-page-language.js +12 -0
  50. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  51. package/src/lib/jobs/ai-job-page-step.d.ts +13 -0
  52. package/src/lib/jobs/ai-job-page-step.js +46 -4
  53. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-plan-step.d.ts +42 -0
  55. package/src/lib/jobs/ai-job-plan-step.js +118 -18
  56. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-site-content.d.ts +49 -3
  58. package/src/lib/jobs/ai-job-site-content.js +196 -37
  59. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  60. package/src/lib/jobs/ai-job-site-step.js +49 -16
  61. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-template-step.js +4 -1
  63. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  64. package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
  65. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  66. package/src/lib/jobs/ai-jobs.d.ts +55 -13
  67. package/src/lib/jobs/ai-jobs.js +213 -41
  68. package/src/lib/jobs/ai-jobs.js.map +1 -1
  69. package/src/lib/jobs/ai-layout-stock-photos.d.ts +194 -34
  70. package/src/lib/jobs/ai-layout-stock-photos.js +861 -146
  71. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  72. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  73. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  74. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  75. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  76. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  77. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  78. package/src/lib/layout-language/ai-layout-compiler.d.ts +33 -0
  79. package/src/lib/layout-language/ai-layout-compiler.js +381 -18
  80. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  81. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  82. package/src/lib/layout-language/ai-layout-design.js +5 -3
  83. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  84. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  85. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  86. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  87. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  88. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  89. package/src/lib/layout-language/ai-layout-language.js +1 -1
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
  92. package/src/lib/layout-language/ai-layout-listings.js +117 -47
  93. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  94. package/src/lib/layout-language/ai-layout-pictures.d.ts +10 -1
  95. package/src/lib/layout-language/ai-layout-pictures.js +5 -2
  96. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  98. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  99. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  100. package/src/lib/model/ai-build-job.d.ts +39 -0
  101. package/src/lib/model/ai-build-job.js +100 -1
  102. package/src/lib/model/ai-build-job.js.map +1 -1
  103. package/src/lib/model/ai-build-progress.d.ts +8 -0
  104. package/src/lib/model/ai-build-progress.js +35 -0
  105. package/src/lib/model/ai-build-progress.js.map +1 -1
  106. package/src/lib/model/ai-credit-estimate.d.ts +249 -0
  107. package/src/lib/model/ai-credit-estimate.js +278 -0
  108. package/src/lib/model/ai-credit-estimate.js.map +1 -0
  109. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  110. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  111. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  112. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  113. package/src/lib/model/ai-job-failure-copy.js +8 -3
  114. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  115. package/src/lib/model/ai-jobs.types.d.ts +35 -0
  116. package/src/lib/model/ai-jobs.types.js.map +1 -1
  117. package/src/lib/model/ai-site-build-progress.js +7 -4
  118. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  119. package/src/lib/model/ai-site-job.d.ts +76 -22
  120. package/src/lib/model/ai-site-job.js +125 -23
  121. package/src/lib/model/ai-site-job.js.map +1 -1
  122. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  123. package/src/lib/model/ai-site-kinds.js +35 -7
  124. package/src/lib/model/ai-site-kinds.js.map +1 -1
  125. package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
  126. package/src/lib/runtime/ai-doctrine-validators.js +161 -7
  127. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  128. package/src/lib/runtime/ai-doctrine.js +4 -3
  129. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  130. package/src/lib/runtime/ai-node-tree.d.ts +11 -0
  131. package/src/lib/runtime/ai-node-tree.js +26 -0
  132. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  133. package/src/lib/runtime/ai-palette.generated.js +592 -14
  134. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  135. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  136. package/src/lib/server/ai-jobs-cancel.js +27 -9
  137. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  138. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  139. package/src/lib/server/ai-jobs-gate.js +20 -9
  140. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  141. package/src/lib/server/ai-jobs-resume.js +93 -35
  142. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  143. package/src/lib/server/ai-jobs-route.js +45 -4
  144. package/src/lib/server/ai-jobs-route.js.map +1 -1
  145. package/src/lib/server/assist-chat.js +4 -1
  146. package/src/lib/server/assist-chat.js.map +1 -1
  147. package/src/lib/server/assist-edit.js +3 -1
  148. package/src/lib/server/assist-edit.js.map +1 -1
  149. package/src/lib/server/assist-music.d.ts +36 -0
  150. package/src/lib/server/assist-music.js +36 -0
  151. package/src/lib/server/assist-music.js.map +1 -0
  152. package/src/lib/server.js +6 -0
  153. package/src/lib/server.js.map +1 -1
  154. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  155. package/src/lib/usage/ai-usage-meter.js +17 -1
  156. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  157. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  158. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  159. package/src/lib/usage/assist-usage.d.ts +7 -0
  160. package/src/lib/usage/assist-usage.js +14 -4
  161. package/src/lib/usage/assist-usage.js.map +1 -1
  162. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  163. package/src/lib/usage/free-ai-credits-left.js +4 -2
  164. package/src/lib/usage/free-ai-credits-left.js.map +1 -1
@@ -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 registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest })\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"],"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 { 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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"}
@@ -25,7 +25,8 @@ import { type AiUsageMeterWire } from './ai-usage-wire';
25
25
  * Every figure is credits. The pool comes through `publicAssistQuota`, the
26
26
  * one projection allowed to turn our provider bill into a customer figure;
27
27
  * the caller's month comes off the allotment gate, which the meter wrote in
28
- * credits in the first place.
28
+ * credits in the first place — or, on the Free plan, off the person's Free
29
+ * allowance the reservation read, net of give-backs (AGL-3722).
29
30
  *
30
31
  * `last` is added to both months, because the reservation measured them
31
32
  * BEFORE this exchange and the strip is read after it.
@@ -16,6 +16,8 @@
16
16
  */ import { aiCatalogEntry } from "../providers/catalog.js";
17
17
  import { publicAssistQuota } from "./assist-usage.js";
18
18
  import { aiUsageMeterState } from "./ai-usage-wire.js";
19
+ import { assistCreditsFromUsd } from "./assist-credits.js";
20
+ import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
19
21
  /**
20
22
  * The usage strip's envelope from what a door already holds (AGL-2942): the
21
23
  * reservation it took and, once the provider answered, what the exchange
@@ -25,7 +27,8 @@ import { aiUsageMeterState } from "./ai-usage-wire.js";
25
27
  * Every figure is credits. The pool comes through `publicAssistQuota`, the
26
28
  * one projection allowed to turn our provider bill into a customer figure;
27
29
  * the caller's month comes off the allotment gate, which the meter wrote in
28
- * credits in the first place.
30
+ * credits in the first place — or, on the Free plan, off the person's Free
31
+ * allowance the reservation read, net of give-backs (AGL-3722).
29
32
  *
30
33
  * `last` is added to both months, because the reservation measured them
31
34
  * BEFORE this exchange and the strip is read after it.
@@ -50,11 +53,24 @@ import { aiUsageMeterState } from "./ai-usage-wire.js";
50
53
  }
51
54
  const gate = (_reservation_allotment = reservation.allotment) != null ? _reservation_allotment : null;
52
55
  const binding = (_ref = gate == null ? void 0 : gate.binding) != null ? _ref : null;
56
+ // On the Free plan the person's own line is their Free allowance (AGL-3722):
57
+ // 300 credits a month across every Free workspace they hold, net of
58
+ // give-backs — the wall the reservation refuses at. The workspace's
59
+ // per-member row it used to read is gross (a refund or a staff reset never
60
+ // reaches it) and this workspace's alone, so it said "You: 401 credits this
61
+ // month" to a person with 48 of their 300 left (2026-10-10).
62
+ const freeAccountUsd = reservation.free ? reservation.freeAccountCostUsd : null;
53
63
  const mine = binding ? {
54
64
  used: binding.used + spent,
55
65
  limit: binding.credits,
56
66
  mode: binding.mode,
57
67
  scope: binding.scope
68
+ } : typeof freeAccountUsd === 'number' && Number.isFinite(freeAccountUsd) ? {
69
+ used: assistCreditsFromUsd(freeAccountUsd) + spent,
70
+ limit: FREE_AI_TASTE_CREDITS_PER_MONTH,
71
+ mode: 'hard',
72
+ scope: null,
73
+ free: true
58
74
  } : {
59
75
  used: ((_ref1 = gate == null ? void 0 : gate.personalCredits) != null ? _ref1 : 0) + spent,
60
76
  limit: null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/ai-usage-meter.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 { aiCatalogEntry } from '../providers/catalog'\nimport { publicAssistQuota, type AssistReservation } from './assist-usage'\nimport { aiUsageMeterState, type AiUsageMeterWire } from './ai-usage-wire'\n\n/**\n * The usage strip's envelope from what a door already holds (AGL-2942): the\n * reservation it took and, once the provider answered, what the exchange\n * cost. No read — the reservation measured the pool and the caller's month\n * inside the transaction it already runs.\n *\n * Every figure is credits. The pool comes through `publicAssistQuota`, the\n * one projection allowed to turn our provider bill into a customer figure;\n * the caller's month comes off the allotment gate, which the meter wrote in\n * credits in the first place.\n *\n * `last` is added to both months, because the reservation measured them\n * BEFORE this exchange and the strip is read after it.\n */\nexport function aiUsageMeter(\n reservation: Pick<AssistReservation, 'monthKey' | 'allowed'> &\n Partial<AssistReservation>,\n extra: {\n lastCredits?: number | null\n model?: { id: string; auto: boolean } | null\n } = {},\n): AiUsageMeterWire {\n const last =\n typeof extra.lastCredits === 'number' && Number.isFinite(extra.lastCredits)\n ? Math.max(0, Math.ceil(extra.lastCredits))\n : null\n const spent = last ?? 0\n let pool: AiUsageMeterWire['pool'] = { used: spent, limit: null }\n try {\n const credits = publicAssistQuota(reservation as AssistReservation)?.credits\n if (credits) pool = { used: credits.used + spent, limit: credits.limit }\n } catch {\n // A reservation without its dollar figures (a double, a refusal taken\n // before the month was read) reports the exchange alone.\n }\n const gate = reservation.allotment ?? null\n const binding = gate?.binding ?? null\n const mine: AiUsageMeterWire['mine'] = binding\n ? {\n used: binding.used + spent,\n limit: binding.credits,\n mode: binding.mode,\n scope: binding.scope,\n }\n : {\n used: (gate?.personalCredits ?? 0) + spent,\n limit: null,\n mode: null,\n scope: null,\n }\n const refused = !reservation.allowed\n const entry = extra.model ? aiCatalogEntry(extra.model.id) : undefined\n return {\n month: reservation.monthKey,\n pool,\n mine,\n last,\n refused,\n state: aiUsageMeterState({ pool, mine, refused }),\n model:\n extra.model && entry\n ? { id: entry.id, label: entry.label, auto: extra.model.auto }\n : null,\n }\n}\n"],"names":["aiCatalogEntry","publicAssistQuota","aiUsageMeterState","aiUsageMeter","reservation","extra","last","lastCredits","Number","isFinite","Math","max","ceil","spent","pool","used","limit","credits","gate","allotment","binding","mine","mode","scope","personalCredits","refused","allowed","entry","model","id","undefined","month","monthKey","state","label","auto"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,cAAc,QAAQ,0BAAsB;AACrD,SAASC,iBAAiB,QAAgC,oBAAgB;AAC1E,SAASC,iBAAiB,QAA+B,qBAAiB;AAE1E;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,aACdC,WAC4B,EAC5BC,QAGI,CAAC,CAAC;QAeOD;IAbb,MAAME,OACJ,OAAOD,MAAME,WAAW,KAAK,YAAYC,OAAOC,QAAQ,CAACJ,MAAME,WAAW,IACtEG,KAAKC,GAAG,CAAC,GAAGD,KAAKE,IAAI,CAACP,MAAME,WAAW,KACvC;IACN,MAAMM,QAAQP,eAAAA,OAAQ;IACtB,IAAIQ,OAAiC;QAAEC,MAAMF;QAAOG,OAAO;IAAK;IAChE,IAAI;YACcf;QAAhB,MAAMgB,WAAUhB,qBAAAA,kBAAkBG,iCAAlBH,mBAAqDgB,OAAO;QAC5E,IAAIA,SAASH,OAAO;YAAEC,MAAME,QAAQF,IAAI,GAAGF;YAAOG,OAAOC,QAAQD,KAAK;QAAC;IACzE,EAAE,eAAM;IACN,sEAAsE;IACtE,yDAAyD;IAC3D;IACA,MAAME,QAAOd,yBAAAA,YAAYe,SAAS,YAArBf,yBAAyB;IACtC,MAAMgB,kBAAUF,wBAAAA,KAAME,OAAO,mBAAI;IACjC,MAAMC,OAAiCD,UACnC;QACEL,MAAMK,QAAQL,IAAI,GAAGF;QACrBG,OAAOI,QAAQH,OAAO;QACtBK,MAAMF,QAAQE,IAAI;QAClBC,OAAOH,QAAQG,KAAK;IACtB,IACA;QACER,MAAM,UAACG,wBAAAA,KAAMM,eAAe,oBAAI,KAAKX;QACrCG,OAAO;QACPM,MAAM;QACNC,OAAO;IACT;IACJ,MAAME,UAAU,CAACrB,YAAYsB,OAAO;IACpC,MAAMC,QAAQtB,MAAMuB,KAAK,GAAG5B,eAAeK,MAAMuB,KAAK,CAACC,EAAE,IAAIC;IAC7D,OAAO;QACLC,OAAO3B,YAAY4B,QAAQ;QAC3BlB;QACAO;QACAf;QACAmB;QACAQ,OAAO/B,kBAAkB;YAAEY;YAAMO;YAAMI;QAAQ;QAC/CG,OACEvB,MAAMuB,KAAK,IAAID,QACX;YAAEE,IAAIF,MAAME,EAAE;YAAEK,OAAOP,MAAMO,KAAK;YAAEC,MAAM9B,MAAMuB,KAAK,CAACO,IAAI;QAAC,IAC3D;IACR;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/ai-usage-meter.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 { aiCatalogEntry } from '../providers/catalog'\nimport { publicAssistQuota, type AssistReservation } from './assist-usage'\nimport { aiUsageMeterState, type AiUsageMeterWire } from './ai-usage-wire'\nimport { assistCreditsFromUsd } from './assist-credits'\nimport { FREE_AI_TASTE_CREDITS_PER_MONTH } from '../plan-entitlements'\n\n/**\n * The usage strip's envelope from what a door already holds (AGL-2942): the\n * reservation it took and, once the provider answered, what the exchange\n * cost. No read — the reservation measured the pool and the caller's month\n * inside the transaction it already runs.\n *\n * Every figure is credits. The pool comes through `publicAssistQuota`, the\n * one projection allowed to turn our provider bill into a customer figure;\n * the caller's month comes off the allotment gate, which the meter wrote in\n * credits in the first place — or, on the Free plan, off the person's Free\n * allowance the reservation read, net of give-backs (AGL-3722).\n *\n * `last` is added to both months, because the reservation measured them\n * BEFORE this exchange and the strip is read after it.\n */\nexport function aiUsageMeter(\n reservation: Pick<AssistReservation, 'monthKey' | 'allowed'> &\n Partial<AssistReservation>,\n extra: {\n lastCredits?: number | null\n model?: { id: string; auto: boolean } | null\n } = {},\n): AiUsageMeterWire {\n const last =\n typeof extra.lastCredits === 'number' && Number.isFinite(extra.lastCredits)\n ? Math.max(0, Math.ceil(extra.lastCredits))\n : null\n const spent = last ?? 0\n let pool: AiUsageMeterWire['pool'] = { used: spent, limit: null }\n try {\n const credits = publicAssistQuota(reservation as AssistReservation)?.credits\n if (credits) pool = { used: credits.used + spent, limit: credits.limit }\n } catch {\n // A reservation without its dollar figures (a double, a refusal taken\n // before the month was read) reports the exchange alone.\n }\n const gate = reservation.allotment ?? null\n const binding = gate?.binding ?? null\n // On the Free plan the person's own line is their Free allowance (AGL-3722):\n // 300 credits a month across every Free workspace they hold, net of\n // give-backs — the wall the reservation refuses at. The workspace's\n // per-member row it used to read is gross (a refund or a staff reset never\n // reaches it) and this workspace's alone, so it said \"You: 401 credits this\n // month\" to a person with 48 of their 300 left (2026-10-10).\n const freeAccountUsd = reservation.free ? reservation.freeAccountCostUsd : null\n const mine: AiUsageMeterWire['mine'] = binding\n ? {\n used: binding.used + spent,\n limit: binding.credits,\n mode: binding.mode,\n scope: binding.scope,\n }\n : typeof freeAccountUsd === 'number' && Number.isFinite(freeAccountUsd)\n ? {\n used: assistCreditsFromUsd(freeAccountUsd) + spent,\n limit: FREE_AI_TASTE_CREDITS_PER_MONTH,\n mode: 'hard',\n scope: null,\n free: true,\n }\n : {\n used: (gate?.personalCredits ?? 0) + spent,\n limit: null,\n mode: null,\n scope: null,\n }\n const refused = !reservation.allowed\n const entry = extra.model ? aiCatalogEntry(extra.model.id) : undefined\n return {\n month: reservation.monthKey,\n pool,\n mine,\n last,\n refused,\n state: aiUsageMeterState({ pool, mine, refused }),\n model:\n extra.model && entry\n ? { id: entry.id, label: entry.label, auto: extra.model.auto }\n : null,\n }\n}\n"],"names":["aiCatalogEntry","publicAssistQuota","aiUsageMeterState","assistCreditsFromUsd","FREE_AI_TASTE_CREDITS_PER_MONTH","aiUsageMeter","reservation","extra","last","lastCredits","Number","isFinite","Math","max","ceil","spent","pool","used","limit","credits","gate","allotment","binding","freeAccountUsd","free","freeAccountCostUsd","mine","mode","scope","personalCredits","refused","allowed","entry","model","id","undefined","month","monthKey","state","label","auto"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,cAAc,QAAQ,0BAAsB;AACrD,SAASC,iBAAiB,QAAgC,oBAAgB;AAC1E,SAASC,iBAAiB,QAA+B,qBAAiB;AAC1E,SAASC,oBAAoB,QAAQ,sBAAkB;AACvD,SAASC,+BAA+B,QAAQ,0BAAsB;AAEtE;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,aACdC,WAC4B,EAC5BC,QAGI,CAAC,CAAC;QAeOD;IAbb,MAAME,OACJ,OAAOD,MAAME,WAAW,KAAK,YAAYC,OAAOC,QAAQ,CAACJ,MAAME,WAAW,IACtEG,KAAKC,GAAG,CAAC,GAAGD,KAAKE,IAAI,CAACP,MAAME,WAAW,KACvC;IACN,MAAMM,QAAQP,eAAAA,OAAQ;IACtB,IAAIQ,OAAiC;QAAEC,MAAMF;QAAOG,OAAO;IAAK;IAChE,IAAI;YACcjB;QAAhB,MAAMkB,WAAUlB,qBAAAA,kBAAkBK,iCAAlBL,mBAAqDkB,OAAO;QAC5E,IAAIA,SAASH,OAAO;YAAEC,MAAME,QAAQF,IAAI,GAAGF;YAAOG,OAAOC,QAAQD,KAAK;QAAC;IACzE,EAAE,eAAM;IACN,sEAAsE;IACtE,yDAAyD;IAC3D;IACA,MAAME,QAAOd,yBAAAA,YAAYe,SAAS,YAArBf,yBAAyB;IACtC,MAAMgB,kBAAUF,wBAAAA,KAAME,OAAO,mBAAI;IACjC,6EAA6E;IAC7E,oEAAoE;IACpE,oEAAoE;IACpE,2EAA2E;IAC3E,4EAA4E;IAC5E,6DAA6D;IAC7D,MAAMC,iBAAiBjB,YAAYkB,IAAI,GAAGlB,YAAYmB,kBAAkB,GAAG;IAC3E,MAAMC,OAAiCJ,UACnC;QACEL,MAAMK,QAAQL,IAAI,GAAGF;QACrBG,OAAOI,QAAQH,OAAO;QACtBQ,MAAML,QAAQK,IAAI;QAClBC,OAAON,QAAQM,KAAK;IACtB,IACA,OAAOL,mBAAmB,YAAYb,OAAOC,QAAQ,CAACY,kBACpD;QACEN,MAAMd,qBAAqBoB,kBAAkBR;QAC7CG,OAAOd;QACPuB,MAAM;QACNC,OAAO;QACPJ,MAAM;IACR,IACA;QACEP,MAAM,UAACG,wBAAAA,KAAMS,eAAe,oBAAI,KAAKd;QACrCG,OAAO;QACPS,MAAM;QACNC,OAAO;IACT;IACN,MAAME,UAAU,CAACxB,YAAYyB,OAAO;IACpC,MAAMC,QAAQzB,MAAM0B,KAAK,GAAGjC,eAAeO,MAAM0B,KAAK,CAACC,EAAE,IAAIC;IAC7D,OAAO;QACLC,OAAO9B,YAAY+B,QAAQ;QAC3BrB;QACAU;QACAlB;QACAsB;QACAQ,OAAOpC,kBAAkB;YAAEc;YAAMU;YAAMI;QAAQ;QAC/CG,OACE1B,MAAM0B,KAAK,IAAID,QACX;YAAEE,IAAIF,MAAME,EAAE;YAAEK,OAAOP,MAAMO,KAAK;YAAEC,MAAMjC,MAAM0B,KAAK,CAACO,IAAI;QAAC,IAC3D;IACR;AACF"}
@@ -100,6 +100,12 @@ export interface AiUsageMeterWire {
100
100
  limit: number | null;
101
101
  mode: AiAllotmentMode | null;
102
102
  scope: AiCreditAllotmentScope | null;
103
+ /**
104
+ * `mine` is the person's Free allowance (AGL-3722): `used` of the 300
105
+ * free credits a month every Free workspace they hold draws on, net of
106
+ * give-backs. Absent on a paid workspace.
107
+ */
108
+ free?: boolean;
103
109
  };
104
110
  /** Credits the exchange this envelope answers cost; `null` when no model ran. */
105
111
  last: number | null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/ai-usage-wire.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// By their own entry points rather than the barrel: the route that serves\n// this shape imports it on the server, where the whole library is dead weight.\nimport {\n topAiUsageKinds,\n type AiUsageByUserMonth,\n} from '../model/ai-usage-by-user'\nimport {\n AI_ALLOTMENT_WARN_SHARE,\n type AiAllotmentMode,\n type AiCreditAllotmentScope,\n} from '../model/ai-allotments'\nimport { csvCell } from '@aglyn/aglyn/app-utils/csv-import'\n\n/**\n * What `/api/ai/usage` answers (AGL-2928), shared by the route and the\n * console surfaces that read it — the Usage table, the roster column, the\n * site collaborators card and the member card. Dependency-free on purpose:\n * the route imports the Admin SDK and a client component may not.\n */\n\n/** Header naming the row count the export undertook to send. */\nexport const AI_USAGE_EXPORT_ROWS_HEADER = 'X-Aglyn-Export-Rows'\n\n/** A row as a customer sees it — credits and a share, never `estCostUsd`. */\nexport interface OrgAiUsageRowWire {\n uid: string\n /** The roster's name — display name, else email, else the uid. */\n name: string\n email: string | null\n role: string | null\n credits: number\n /** Of the workspace's measured spend for the month, in `[0, 1]`. */\n share: number\n requests: number\n refusals: number\n byKind: Record<string, number>\n byHost: Record<string, number>\n /** Credits on the site the request named, when it named one. */\n hostCredits?: number\n}\n\nexport interface OrgAiUsageTableWire {\n month: string\n /** The months a reader may ask for, newest first. */\n months: string[]\n /** The workspace's own credits for the month — the pool the shares are of. */\n orgCredits: number\n rows: OrgAiUsageRowWire[]\n}\n\n/** One person's month, as the member card reads it. */\nexport interface UserAiUsageMonthWire {\n month: string\n credits: number\n share: number\n requests: number\n refusals: number\n byKind: Record<string, number>\n byHost: Record<string, number>\n}\n\nexport interface UserAiUsageWire {\n uid: string\n /** Newest first. */\n months: UserAiUsageMonthWire[]\n}\n\n/** The roster's month, joined and shared, projected onto the wire. */\nexport function toAiUsageWireRow(\n row: AiUsageByUserMonth & {\n name: string\n email: string | null\n role: string | null\n share: number\n },\n hostId: string,\n): OrgAiUsageRowWire {\n const wire: OrgAiUsageRowWire = {\n uid: row.uid,\n name: row.name,\n email: row.email,\n role: row.role,\n credits: row.credits,\n share: row.share,\n requests: row.requests,\n refusals: row.refusals,\n byKind: row.byKind,\n byHost: row.byHost,\n }\n if (hostId) wire.hostCredits = row.byHost[hostId] ?? 0\n return wire\n}\n\n/** The CSV's columns, in order. */\nexport const AI_USAGE_CSV_HEADER = [\n 'Member',\n 'Email',\n 'Role',\n 'Credits',\n 'Share',\n 'Requests',\n 'Refusals',\n 'Top kinds',\n 'Sites',\n] as const\n\n/** A share as the table and the file print it: one decimal of a percent. */\nexport function formatAiUsageShare(share: number): string {\n return `${Math.round(share * 1000) / 10}%`\n}\n\nexport function aiUsageCsvLine(row: OrgAiUsageRowWire): string {\n const kinds = topAiUsageKinds(row.byKind, 3)\n .map((entry) => `${entry.kind} ${entry.credits}`)\n .join('; ')\n const sites = Object.entries(row.byHost)\n .sort((a, b) => b[1] - a[1])\n .map(([hostId, credits]) => `${hostId} ${credits}`)\n .join('; ')\n return [\n row.name,\n row.email ?? '',\n row.role ?? '',\n row.credits,\n formatAiUsageShare(row.share),\n row.requests,\n row.refusals,\n kinds,\n sites,\n ]\n .map(csvCell)\n .join(',')\n}\n\n/**\n * THE USAGE STRIP'S ENVELOPE (AGL-2942): what every AI door adds to the\n * answer it already sends — the chat door's `done` event, the copy\n * assistant's JSON, a job's create response, and each door's quota refusal —\n * so the strip in the panel and the generation dialogs updates with no read\n * of its own.\n *\n * Credits only. `pool` is the workspace's month against its band; `mine` is\n * the caller's own month, measured against the allotment that binds them\n * when one does — for a site allotment, the site's month, because that is\n * the line the next request meets.\n */\nexport type AiUsageMeterState = 'ok' | 'warn' | 'capped'\n\nexport interface AiUsageMeterWire {\n month: string\n pool: { used: number; limit: number | null }\n mine: {\n used: number\n limit: number | null\n mode: AiAllotmentMode | null\n scope: AiCreditAllotmentScope | null\n }\n /** Credits the exchange this envelope answers cost; `null` when no model ran. */\n last: number | null\n /** True when the envelope rides a refusal. */\n refused: boolean\n state: AiUsageMeterState\n /** The model that answered, as the catalog names it. */\n model: { id: string; label: string; auto: boolean } | null\n}\n\n/**\n * `capped` when a hard allotment is spent or the request was refused, `warn`\n * from 80% of the allotment that binds or of the pool, `ok` otherwise.\n */\nexport function aiUsageMeterState(\n meter: Pick<AiUsageMeterWire, 'pool' | 'mine' | 'refused'>,\n): AiUsageMeterState {\n const { mine, pool } = meter\n if (meter.refused) return 'capped'\n if (mine.limit !== null && mine.mode === 'hard' && mine.used >= mine.limit) return 'capped'\n const past = (used: number, limit: number | null) =>\n limit !== null && limit > 0 && used >= limit * AI_ALLOTMENT_WARN_SHARE\n return past(mine.used, mine.limit) || past(pool.used, pool.limit) ? 'warn' : 'ok'\n}\n\n/**\n * What `/api/ai/allotments` answers (AGL-2942): the allotments a reader may\n * see, each with what its subject drew this month, and the roster, sites\n * and models the editors pick from. Credits only.\n */\nexport interface AiAllotmentRowWire {\n subject: string\n scope: 'member' | 'collab' | 'host' | 'org'\n uid: string | null\n hostId: string | null\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n /** Credits the subject drew this month — the figure the gate measures. */\n used: number\n}\n\nexport interface AiAllotmentsMemberWire {\n uid: string\n name: string\n email: string | null\n role: string | null\n /** False for a site collaborator, whose allotments are per site. */\n orgWide: boolean\n /** Credits this month, every site. */\n credits: number\n /** Credits this month by site. */\n byHost: Record<string, number>\n}\n\nexport interface AiAllotmentsHostWire {\n hostId: string\n name: string\n /** The site's credits this month, everyone on it. */\n credits: number\n}\n\nexport interface AiAllotmentsWire {\n /** The org the answer is about — resolved from the site when only one was named. */\n orgId: string\n month: string\n pool: { used: number; limit: number | null }\n allotments: AiAllotmentRowWire[]\n members: AiAllotmentsMemberWire[]\n hosts: AiAllotmentsHostWire[]\n /** The catalog models this deployment serves, for an allowlist. */\n models: Array<{ id: string; label: string; tier: string }>\n /** Whether the plan lets the workspace restrict models org-wide. */\n restrictionAvailable: boolean\n canEdit: {\n /** Members, sites and the restriction: `billing.manage`. */\n billing: boolean\n /** Collaborators on the site asked about: `billing.manage`, or its admin. */\n collaborators: boolean\n }\n /** Who is reading, so an editor can refuse to offer a self-raise. */\n callerUid: string\n}\n\n/** `2026-09` as a reader says it, on the UTC calendar the key is cut from. */\nexport function aiUsageMonthLabel(month: string): string {\n const [year, monthOfYear] = month.split('-').map(Number)\n if (!year || !monthOfYear) return month\n try {\n return new Date(Date.UTC(year, monthOfYear - 1, 1)).toLocaleDateString(undefined, {\n month: 'long',\n year: 'numeric',\n timeZone: 'UTC',\n })\n } catch {\n return month\n }\n}\n"],"names":["topAiUsageKinds","AI_ALLOTMENT_WARN_SHARE","csvCell","AI_USAGE_EXPORT_ROWS_HEADER","toAiUsageWireRow","row","hostId","wire","uid","name","email","role","credits","share","requests","refusals","byKind","byHost","hostCredits","AI_USAGE_CSV_HEADER","formatAiUsageShare","Math","round","aiUsageCsvLine","kinds","map","entry","kind","join","sites","Object","entries","sort","a","b","aiUsageMeterState","meter","mine","pool","refused","limit","mode","used","past","aiUsageMonthLabel","month","year","monthOfYear","split","Number","Date","UTC","toLocaleDateString","undefined","timeZone"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/ai-usage-wire.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// By their own entry points rather than the barrel: the route that serves\n// this shape imports it on the server, where the whole library is dead weight.\nimport {\n topAiUsageKinds,\n type AiUsageByUserMonth,\n} from '../model/ai-usage-by-user'\nimport {\n AI_ALLOTMENT_WARN_SHARE,\n type AiAllotmentMode,\n type AiCreditAllotmentScope,\n} from '../model/ai-allotments'\nimport { csvCell } from '@aglyn/aglyn/app-utils/csv-import'\n\n/**\n * What `/api/ai/usage` answers (AGL-2928), shared by the route and the\n * console surfaces that read it — the Usage table, the roster column, the\n * site collaborators card and the member card. Dependency-free on purpose:\n * the route imports the Admin SDK and a client component may not.\n */\n\n/** Header naming the row count the export undertook to send. */\nexport const AI_USAGE_EXPORT_ROWS_HEADER = 'X-Aglyn-Export-Rows'\n\n/** A row as a customer sees it — credits and a share, never `estCostUsd`. */\nexport interface OrgAiUsageRowWire {\n uid: string\n /** The roster's name — display name, else email, else the uid. */\n name: string\n email: string | null\n role: string | null\n credits: number\n /** Of the workspace's measured spend for the month, in `[0, 1]`. */\n share: number\n requests: number\n refusals: number\n byKind: Record<string, number>\n byHost: Record<string, number>\n /** Credits on the site the request named, when it named one. */\n hostCredits?: number\n}\n\nexport interface OrgAiUsageTableWire {\n month: string\n /** The months a reader may ask for, newest first. */\n months: string[]\n /** The workspace's own credits for the month — the pool the shares are of. */\n orgCredits: number\n rows: OrgAiUsageRowWire[]\n}\n\n/** One person's month, as the member card reads it. */\nexport interface UserAiUsageMonthWire {\n month: string\n credits: number\n share: number\n requests: number\n refusals: number\n byKind: Record<string, number>\n byHost: Record<string, number>\n}\n\nexport interface UserAiUsageWire {\n uid: string\n /** Newest first. */\n months: UserAiUsageMonthWire[]\n}\n\n/** The roster's month, joined and shared, projected onto the wire. */\nexport function toAiUsageWireRow(\n row: AiUsageByUserMonth & {\n name: string\n email: string | null\n role: string | null\n share: number\n },\n hostId: string,\n): OrgAiUsageRowWire {\n const wire: OrgAiUsageRowWire = {\n uid: row.uid,\n name: row.name,\n email: row.email,\n role: row.role,\n credits: row.credits,\n share: row.share,\n requests: row.requests,\n refusals: row.refusals,\n byKind: row.byKind,\n byHost: row.byHost,\n }\n if (hostId) wire.hostCredits = row.byHost[hostId] ?? 0\n return wire\n}\n\n/** The CSV's columns, in order. */\nexport const AI_USAGE_CSV_HEADER = [\n 'Member',\n 'Email',\n 'Role',\n 'Credits',\n 'Share',\n 'Requests',\n 'Refusals',\n 'Top kinds',\n 'Sites',\n] as const\n\n/** A share as the table and the file print it: one decimal of a percent. */\nexport function formatAiUsageShare(share: number): string {\n return `${Math.round(share * 1000) / 10}%`\n}\n\nexport function aiUsageCsvLine(row: OrgAiUsageRowWire): string {\n const kinds = topAiUsageKinds(row.byKind, 3)\n .map((entry) => `${entry.kind} ${entry.credits}`)\n .join('; ')\n const sites = Object.entries(row.byHost)\n .sort((a, b) => b[1] - a[1])\n .map(([hostId, credits]) => `${hostId} ${credits}`)\n .join('; ')\n return [\n row.name,\n row.email ?? '',\n row.role ?? '',\n row.credits,\n formatAiUsageShare(row.share),\n row.requests,\n row.refusals,\n kinds,\n sites,\n ]\n .map(csvCell)\n .join(',')\n}\n\n/**\n * THE USAGE STRIP'S ENVELOPE (AGL-2942): what every AI door adds to the\n * answer it already sends — the chat door's `done` event, the copy\n * assistant's JSON, a job's create response, and each door's quota refusal —\n * so the strip in the panel and the generation dialogs updates with no read\n * of its own.\n *\n * Credits only. `pool` is the workspace's month against its band; `mine` is\n * the caller's own month, measured against the allotment that binds them\n * when one does — for a site allotment, the site's month, because that is\n * the line the next request meets.\n */\nexport type AiUsageMeterState = 'ok' | 'warn' | 'capped'\n\nexport interface AiUsageMeterWire {\n month: string\n pool: { used: number; limit: number | null }\n mine: {\n used: number\n limit: number | null\n mode: AiAllotmentMode | null\n scope: AiCreditAllotmentScope | null\n /**\n * `mine` is the person's Free allowance (AGL-3722): `used` of the 300\n * free credits a month every Free workspace they hold draws on, net of\n * give-backs. Absent on a paid workspace.\n */\n free?: boolean\n }\n /** Credits the exchange this envelope answers cost; `null` when no model ran. */\n last: number | null\n /** True when the envelope rides a refusal. */\n refused: boolean\n state: AiUsageMeterState\n /** The model that answered, as the catalog names it. */\n model: { id: string; label: string; auto: boolean } | null\n}\n\n/**\n * `capped` when a hard allotment is spent or the request was refused, `warn`\n * from 80% of the allotment that binds or of the pool, `ok` otherwise.\n */\nexport function aiUsageMeterState(\n meter: Pick<AiUsageMeterWire, 'pool' | 'mine' | 'refused'>,\n): AiUsageMeterState {\n const { mine, pool } = meter\n if (meter.refused) return 'capped'\n if (mine.limit !== null && mine.mode === 'hard' && mine.used >= mine.limit) return 'capped'\n const past = (used: number, limit: number | null) =>\n limit !== null && limit > 0 && used >= limit * AI_ALLOTMENT_WARN_SHARE\n return past(mine.used, mine.limit) || past(pool.used, pool.limit) ? 'warn' : 'ok'\n}\n\n/**\n * What `/api/ai/allotments` answers (AGL-2942): the allotments a reader may\n * see, each with what its subject drew this month, and the roster, sites\n * and models the editors pick from. Credits only.\n */\nexport interface AiAllotmentRowWire {\n subject: string\n scope: 'member' | 'collab' | 'host' | 'org'\n uid: string | null\n hostId: string | null\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n /** Credits the subject drew this month — the figure the gate measures. */\n used: number\n}\n\nexport interface AiAllotmentsMemberWire {\n uid: string\n name: string\n email: string | null\n role: string | null\n /** False for a site collaborator, whose allotments are per site. */\n orgWide: boolean\n /** Credits this month, every site. */\n credits: number\n /** Credits this month by site. */\n byHost: Record<string, number>\n}\n\nexport interface AiAllotmentsHostWire {\n hostId: string\n name: string\n /** The site's credits this month, everyone on it. */\n credits: number\n}\n\nexport interface AiAllotmentsWire {\n /** The org the answer is about — resolved from the site when only one was named. */\n orgId: string\n month: string\n pool: { used: number; limit: number | null }\n allotments: AiAllotmentRowWire[]\n members: AiAllotmentsMemberWire[]\n hosts: AiAllotmentsHostWire[]\n /** The catalog models this deployment serves, for an allowlist. */\n models: Array<{ id: string; label: string; tier: string }>\n /** Whether the plan lets the workspace restrict models org-wide. */\n restrictionAvailable: boolean\n canEdit: {\n /** Members, sites and the restriction: `billing.manage`. */\n billing: boolean\n /** Collaborators on the site asked about: `billing.manage`, or its admin. */\n collaborators: boolean\n }\n /** Who is reading, so an editor can refuse to offer a self-raise. */\n callerUid: string\n}\n\n/** `2026-09` as a reader says it, on the UTC calendar the key is cut from. */\nexport function aiUsageMonthLabel(month: string): string {\n const [year, monthOfYear] = month.split('-').map(Number)\n if (!year || !monthOfYear) return month\n try {\n return new Date(Date.UTC(year, monthOfYear - 1, 1)).toLocaleDateString(undefined, {\n month: 'long',\n year: 'numeric',\n timeZone: 'UTC',\n })\n } catch {\n return month\n }\n}\n"],"names":["topAiUsageKinds","AI_ALLOTMENT_WARN_SHARE","csvCell","AI_USAGE_EXPORT_ROWS_HEADER","toAiUsageWireRow","row","hostId","wire","uid","name","email","role","credits","share","requests","refusals","byKind","byHost","hostCredits","AI_USAGE_CSV_HEADER","formatAiUsageShare","Math","round","aiUsageCsvLine","kinds","map","entry","kind","join","sites","Object","entries","sort","a","b","aiUsageMeterState","meter","mine","pool","refused","limit","mode","used","past","aiUsageMonthLabel","month","year","monthOfYear","split","Number","Date","UTC","toLocaleDateString","undefined","timeZone"],"mappings":"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"}
@@ -269,6 +269,13 @@ export interface AssistReservation extends AssistQuotaVerdict {
269
269
  * on, and the record must charge that account and no other.
270
270
  */
271
271
  free: FreeAssistAccount | null;
272
+ /**
273
+ * The Free account's month in billed USD, net of give-backs, as read inside
274
+ * the transaction (AGL-3722) — the allowance every Free workspace of the
275
+ * person draws on. `null` for a paid workspace and for a reservation that
276
+ * did not read it. Optional so a double without it reads as not read.
277
+ */
278
+ freeAccountCostUsd?: number | null;
272
279
  /**
273
280
  * Measured provider spend for `monthKey` in USD as read inside the
274
281
  * transaction, or `null` when the transaction did not consult it.
@@ -414,6 +414,9 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
414
414
  // Net of any credits given back this month (AGL-3595), so a give-back
415
415
  // re-opens the band it was meant to re-open.
416
416
  const costUsd = monthlySnapshot ? assistSpendAfterReturnsUsd(monthlySnapshot.get('estCostUsd'), monthlySnapshot.get(ASSIST_RETURNED_USD_FIELD)) : null;
417
+ // The person's Free allowance, net of give-backs (AGL-3722): what the
418
+ // usage strip says is theirs, never the workspace's gross per-member row.
419
+ const freeAccountCostUsd = accountSnapshot ? assistSpendAfterReturnsUsd(accountSnapshot.get('estCostUsd'), accountSnapshot.get(ASSIST_RETURNED_USD_FIELD)) : null;
417
420
  if (!(used < limit)) {
418
421
  recordAssistRefusal(firestore, orgId, month, 'messages');
419
422
  return {
@@ -428,7 +431,8 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
428
431
  costUsd,
429
432
  costLimitUsd,
430
433
  budgetUsd,
431
- free
434
+ free,
435
+ freeAccountCostUsd
432
436
  };
433
437
  }
434
438
  // The dollar ceiling, checked AFTER the message cap so the cheaper and
@@ -449,7 +453,8 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
449
453
  costUsd,
450
454
  costLimitUsd,
451
455
  budgetUsd,
452
- free
456
+ free,
457
+ freeAccountCostUsd
453
458
  };
454
459
  }
455
460
  // The org's ceiling on its OVERAGE (AGL-2898), checked after the band
@@ -478,7 +483,8 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
478
483
  costUsd,
479
484
  costLimitUsd,
480
485
  budgetUsd,
481
- free
486
+ free,
487
+ freeAccountCostUsd
482
488
  };
483
489
  }
484
490
  // AGLYN'S OWN OVERAGE GUARDS (AGL-3011), after the workspace's own
@@ -518,7 +524,8 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
518
524
  costUsd,
519
525
  costLimitUsd,
520
526
  budgetUsd,
521
- free
527
+ free,
528
+ freeAccountCostUsd
522
529
  };
523
530
  }
524
531
  // The allotments that apply to the person asking (AGL-2942): read only
@@ -541,6 +548,7 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
541
548
  costLimitUsd,
542
549
  budgetUsd,
543
550
  free,
551
+ freeAccountCostUsd,
544
552
  allotment
545
553
  };
546
554
  }
@@ -563,6 +571,7 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
563
571
  costLimitUsd,
564
572
  budgetUsd,
565
573
  free,
574
+ freeAccountCostUsd,
566
575
  allotment
567
576
  };
568
577
  }
@@ -602,6 +611,7 @@ async function reserveInTransaction(firestore, orgId, entitled, now, org, subjec
602
611
  costLimitUsd,
603
612
  budgetUsd,
604
613
  free,
614
+ freeAccountCostUsd,
605
615
  allotment
606
616
  };
607
617
  });