@aglyn/plugins-ai 1.0.0-beta.225 → 1.0.0-beta.227

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 (161) hide show
  1. package/README.md +2 -1
  2. package/package.json +17 -17
  3. package/src/lib/billing/ai-month-meter.js +5 -3
  4. package/src/lib/billing/ai-month-meter.js.map +1 -1
  5. package/src/lib/billing/ai-overage-ledger.js +3 -1
  6. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  7. package/src/lib/components/ai-brief-dialog.component.d.ts +6 -2
  8. package/src/lib/components/ai-brief-dialog.component.js +35 -17
  9. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  10. package/src/lib/components/ai-describe-automation.component.js +17 -2
  11. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  12. package/src/lib/components/ai-insight-dialog.component.js +24 -2
  13. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  14. package/src/lib/components/ai-job-events.d.ts +14 -0
  15. package/src/lib/components/ai-job-events.js +76 -0
  16. package/src/lib/components/ai-job-events.js.map +1 -0
  17. package/src/lib/components/ai-job-follow.component.d.ts +49 -0
  18. package/src/lib/components/ai-job-follow.component.js +210 -0
  19. package/src/lib/components/ai-job-follow.component.js.map +1 -0
  20. package/src/lib/components/ai-job-links.d.ts +42 -0
  21. package/src/lib/components/ai-job-links.js +140 -0
  22. package/src/lib/components/ai-job-links.js.map +1 -0
  23. package/src/lib/components/ai-job-plan.component.d.ts +13 -6
  24. package/src/lib/components/ai-job-plan.component.js +29 -3
  25. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  26. package/src/lib/components/ai-job-requests.d.ts +24 -0
  27. package/src/lib/components/ai-job-requests.js +76 -0
  28. package/src/lib/components/ai-job-requests.js.map +1 -0
  29. package/src/lib/components/ai-jobs-indicator.component.d.ts +25 -0
  30. package/src/lib/components/ai-jobs-indicator.component.js +80 -0
  31. package/src/lib/components/ai-jobs-indicator.component.js.map +1 -0
  32. package/src/lib/components/ai-jobs-store.d.ts +81 -0
  33. package/src/lib/components/ai-jobs-store.js +218 -0
  34. package/src/lib/components/ai-jobs-store.js.map +1 -0
  35. package/src/lib/components/ai-page-brief-dialog.component.d.ts +3 -3
  36. package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
  37. package/src/lib/components/ai-site-build-page.component.d.ts +23 -0
  38. package/src/lib/components/ai-site-build-page.component.js +377 -0
  39. package/src/lib/components/ai-site-build-page.component.js.map +1 -0
  40. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  41. package/src/lib/components/ai-site-start-card.component.js +522 -130
  42. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  43. package/src/lib/components/ai-site-starter-fallback.component.d.ts +24 -0
  44. package/src/lib/components/ai-site-starter-fallback.component.js +112 -0
  45. package/src/lib/components/ai-site-starter-fallback.component.js.map +1 -0
  46. package/src/lib/components/assist-jobs-drawer.component.d.ts +18 -20
  47. package/src/lib/components/assist-jobs-drawer.component.js +152 -172
  48. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  49. package/src/lib/components/assist-panel.component.js +62 -6
  50. package/src/lib/components/assist-panel.component.js.map +1 -1
  51. package/src/lib/components/staff-ai-credit-return.component.d.ts +55 -0
  52. package/src/lib/components/staff-ai-credit-return.component.js +346 -0
  53. package/src/lib/components/staff-ai-credit-return.component.js.map +1 -0
  54. package/src/lib/components/staff-org-ai-card.component.js +364 -279
  55. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  56. package/src/lib/components/staff-user-ai-usage-card.component.d.ts +4 -0
  57. package/src/lib/components/staff-user-ai-usage-card.component.js +83 -28
  58. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  59. package/src/lib/components/use-ai-job-run.js +5 -18
  60. package/src/lib/components/use-ai-job-run.js.map +1 -1
  61. package/src/lib/jobs/ai-job-auto-confirm.d.ts +52 -0
  62. package/src/lib/jobs/ai-job-auto-confirm.js +62 -0
  63. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -0
  64. package/src/lib/jobs/ai-job-plan-step.d.ts +33 -2
  65. package/src/lib/jobs/ai-job-plan-step.js +161 -17
  66. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-site-step.d.ts +7 -0
  68. package/src/lib/jobs/ai-job-site-step.js +34 -4
  69. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  70. package/src/lib/jobs/ai-job-text-step.d.ts +7 -0
  71. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  72. package/src/lib/jobs/ai-jobs-notify.d.ts +37 -0
  73. package/src/lib/jobs/ai-jobs-notify.js +51 -0
  74. package/src/lib/jobs/ai-jobs-notify.js.map +1 -0
  75. package/src/lib/jobs/ai-jobs.d.ts +31 -4
  76. package/src/lib/jobs/ai-jobs.js +196 -32
  77. package/src/lib/jobs/ai-jobs.js.map +1 -1
  78. package/src/lib/model/ai-job-activity.d.ts +112 -0
  79. package/src/lib/model/ai-job-activity.js +144 -0
  80. package/src/lib/model/ai-job-activity.js.map +1 -0
  81. package/src/lib/model/ai-job-failure-copy.d.ts +69 -0
  82. package/src/lib/model/ai-job-failure-copy.js +78 -0
  83. package/src/lib/model/ai-job-failure-copy.js.map +1 -0
  84. package/src/lib/model/ai-job-notice.d.ts +61 -0
  85. package/src/lib/model/ai-job-notice.js +112 -0
  86. package/src/lib/model/ai-job-notice.js.map +1 -0
  87. package/src/lib/model/ai-jobs.types.d.ts +26 -0
  88. package/src/lib/model/ai-jobs.types.js +1 -0
  89. package/src/lib/model/ai-jobs.types.js.map +1 -1
  90. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  91. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  92. package/src/lib/model/ai-site-build-progress.d.ts +45 -0
  93. package/src/lib/model/ai-site-build-progress.js +76 -0
  94. package/src/lib/model/ai-site-build-progress.js.map +1 -0
  95. package/src/lib/model/ai-site-inventory.d.ts +9 -0
  96. package/src/lib/model/ai-site-inventory.js +7 -2
  97. package/src/lib/model/ai-site-inventory.js.map +1 -1
  98. package/src/lib/model/ai-site-job.d.ts +93 -2
  99. package/src/lib/model/ai-site-job.js +88 -7
  100. package/src/lib/model/ai-site-job.js.map +1 -1
  101. package/src/lib/model/ai-site-start.d.ts +3 -1
  102. package/src/lib/model/ai-site-start.js +6 -4
  103. package/src/lib/model/ai-site-start.js.map +1 -1
  104. package/src/lib/model/ai-template-subjects.d.ts +4 -3
  105. package/src/lib/model/ai-template-subjects.js +5 -4
  106. package/src/lib/model/ai-template-subjects.js.map +1 -1
  107. package/src/lib/plan-entitlements.d.ts +3 -3
  108. package/src/lib/plan-entitlements.js +3 -3
  109. package/src/lib/plan-entitlements.js.map +1 -1
  110. package/src/lib/plugin.js +28 -0
  111. package/src/lib/plugin.js.map +1 -1
  112. package/src/lib/runtime/ai-abuse-guards.d.ts +9 -6
  113. package/src/lib/runtime/ai-abuse-guards.js +10 -7
  114. package/src/lib/runtime/ai-abuse-guards.js.map +1 -1
  115. package/src/lib/runtime/ai-doctrine-validators.js +31 -4
  116. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  117. package/src/lib/runtime/ai-gate.d.ts +3 -2
  118. package/src/lib/runtime/ai-gate.js +3 -2
  119. package/src/lib/runtime/ai-gate.js.map +1 -1
  120. package/src/lib/runtime/ai-palette.generated.js +12 -4
  121. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  122. package/src/lib/runtime/site-inventory.d.ts +16 -1
  123. package/src/lib/runtime/site-inventory.js +43 -3
  124. package/src/lib/runtime/site-inventory.js.map +1 -1
  125. package/src/lib/server/ai-admin-credits.d.ts +21 -0
  126. package/src/lib/server/ai-admin-credits.js +213 -0
  127. package/src/lib/server/ai-admin-credits.js.map +1 -0
  128. package/src/lib/server/ai-admin-org.js +12 -5
  129. package/src/lib/server/ai-admin-org.js.map +1 -1
  130. package/src/lib/server/ai-admin-signals.js +3 -1
  131. package/src/lib/server/ai-admin-signals.js.map +1 -1
  132. package/src/lib/server/ai-admin-user.js +12 -1
  133. package/src/lib/server/ai-admin-user.js.map +1 -1
  134. package/src/lib/server/ai-allotments.js +3 -2
  135. package/src/lib/server/ai-allotments.js.map +1 -1
  136. package/src/lib/server/ai-jobs-route.js +20 -2
  137. package/src/lib/server/ai-jobs-route.js.map +1 -1
  138. package/src/lib/server/assist-chat.js +3 -2
  139. package/src/lib/server/assist-chat.js.map +1 -1
  140. package/src/lib/server/billing-credits.js +4 -3
  141. package/src/lib/server/billing-credits.js.map +1 -1
  142. package/src/lib/server/billing-overage.js +3 -2
  143. package/src/lib/server/billing-overage.js.map +1 -1
  144. package/src/lib/server.js +11 -0
  145. package/src/lib/server.js.map +1 -1
  146. package/src/lib/usage/assist-credit-returns-write.d.ts +75 -0
  147. package/src/lib/usage/assist-credit-returns-write.js +150 -0
  148. package/src/lib/usage/assist-credit-returns-write.js.map +1 -0
  149. package/src/lib/usage/assist-credit-returns.d.ts +68 -0
  150. package/src/lib/usage/assist-credit-returns.js +79 -0
  151. package/src/lib/usage/assist-credit-returns.js.map +1 -0
  152. package/src/lib/usage/assist-free-taste.js +6 -3
  153. package/src/lib/usage/assist-free-taste.js.map +1 -1
  154. package/src/lib/usage/assist-job-refund.d.ts +100 -0
  155. package/src/lib/usage/assist-job-refund.js +109 -0
  156. package/src/lib/usage/assist-job-refund.js.map +1 -0
  157. package/src/lib/usage/assist-usage.js +5 -2
  158. package/src/lib/usage/assist-usage.js.map +1 -1
  159. package/src/lib/usage/staff-org-ai.d.ts +31 -1
  160. package/src/lib/usage/staff-org-ai.js +19 -2
  161. package/src/lib/usage/staff-org-ai.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-allotments.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n assistCreditsFromUsd,\n resolveAssistCreditBudget,\n} from '../usage/assist-credits'\nimport { hostRoleFor, isOrgWideMember } from '@aglyn/aglyn/app-utils/organizations'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { invalidIdTokenResponse } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { logAiAllotmentChanged } from '../activity/ai-activity'\nimport {\n AI_ALLOTMENT_MAX_CREDITS,\n AI_ALLOTMENT_MAX_MODELS,\n aiAllotmentCredits,\n parseAiAllotmentSubject,\n type AiAllotment,\n type AiAllotmentMode,\n type AiAllotmentSubject,\n} from '../model/ai-allotments'\nimport { aiUsageByUserMonthFrom, aiUsageMonthKeys } from '../model/ai-usage-by-user'\nimport { AI_MODEL_CATALOG } from '../providers/catalog'\nimport { AI_MODEL_RESTRICTION_PLANS } from '../providers/model-choice'\nimport { resolveAiProvider } from '../providers/routing'\nimport {\n AI_HOST_CREDITS_FIELD,\n applyAiAllotmentWrites,\n listAiAllotments,\n seedAiHostMonthCredits,\n type AiAllotmentWrite,\n} from '../usage/ai-allotments'\nimport { userAiUsageMonthRef } from '../usage/ai-usage-by-user'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n AiAllotmentsWire,\n} from '../usage/ai-usage-wire'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\n\n// lockdown-423: exempt — a spend control, the posture of ai/billing/overage\n// beside it: a billing-locked workspace must still be able to LOWER what its\n// people may spend, and a 423 would trap it spending.\n\n/**\n * AI ALLOTMENTS (AGL-2942) — read and set them.\n *\n * ## Who may read\n *\n * - The whole org's allotments (`orgId`): `billing.view`, the Usage page's\n * gate — every allotment is a figure about the pool it is part of.\n * - One site's (`hostId`): the same, or the site's admin when they are a\n * collaborator scoped to it — the collaborators card is theirs.\n * - One person's (`uid`): the same, or the person themselves.\n *\n * ## Who may write\n *\n * - A member's, a site's and the org-wide model restriction:\n * `billing.manage`. Each moves how much of the paid pool someone may\n * spend.\n * - A collaborator's on one site: `billing.manage`, or the admin of that\n * site when they are a collaborator scoped to it — never on their own\n * allotment, which they could otherwise raise.\n *\n * Staff pass, as at the other org routes. The org-wide restriction is set\n * only on a plan that offers it (`AI_MODEL_RESTRICTION_PLANS`); clearing it\n * is always allowed.\n *\n * ## One write, many subjects\n *\n * `POST { orgId, set, remove, everySite }`: `set` replaces allotments,\n * `remove` deletes them, and `everySite` sets the same allotment on every\n * site the org has — \"same for every client site\" — expanded here, from the\n * org's sites, rather than trusted from a client's list.\n */\n\nconst json = (body: unknown, status: number) => Response.json(body, { status })\n\n/** Batch writes a request may make; Firestore commits at most 500. */\nconst MAX_WRITES = 450\n\n/** Sites one `everySite` expands to. */\nconst MAX_SITES = 400\n\n/** Member months one `getAll` carries. */\nconst GET_ALL_CHUNK = 100\n\ninterface Caller {\n uid: string\n email: string | null\n staff: boolean\n member: AglynOrgMember | null\n}\n\nasync function authenticate(\n headers: Partial<Record<string, string>>,\n): Promise<Response | { decoded: { uid: string; email?: string; staff: boolean } }> {\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return json({ error: 'Unauthenticated' }, 401)\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n return { decoded: { uid: decoded.uid, email: decoded.email, staff: decoded['staff'] === true } }\n}\n\n/** A collaborator scoped to the site who holds its admin role there. */\nfunction isSiteAdminCollaborator(member: AglynOrgMember | null, hostId: string): boolean {\n if (!member || !hostId || isOrgWideMember(member)) return false\n return hostRoleFor(member, hostId) === 'admin'\n}\n\ninterface RosterMonth {\n member: AglynOrgMember\n credits: number\n byHost: Record<string, number>\n}\n\n/** The roster and each member's month, by path — no index, one getAll a hundred. */\nasync function readRosterMonths(\n firestore: FirebaseFirestore.Firestore,\n orgRef: FirebaseFirestore.DocumentReference,\n month: string,\n): Promise<RosterMonth[]> {\n const roster = await orgRef.collection('members').get()\n const members = roster.docs.map(\n (doc) => ({ $id: doc.id, ...doc.data() }) as AglynOrgMember,\n )\n const out: RosterMonth[] = []\n for (let start = 0; start < members.length; start += GET_ALL_CHUNK) {\n const chunk = members.slice(start, start + GET_ALL_CHUNK)\n const snapshots = chunk.length\n ? await firestore.getAll(\n ...chunk.map((member) => userAiUsageMonthRef(orgRef, member.$id, month)),\n )\n : []\n snapshots.forEach((snapshot, index) => {\n const member = chunk[index]\n const usage = aiUsageByUserMonthFrom(\n snapshot.exists ? (snapshot.data() as Record<string, unknown>) : null,\n member.$id,\n month,\n )\n out.push({ member, credits: usage.credits, byHost: usage.byHost })\n })\n }\n return out\n}\n\nconst memberName = (member: AglynOrgMember): string =>\n String(member.displayName ?? '').trim() || String(member.email ?? '').trim() || member.$id\n\n/** A site's credits this month: the org map, else its people's months summed. */\nfunction siteCredits(\n orgMonth: FirebaseFirestore.DocumentSnapshot,\n roster: readonly RosterMonth[],\n hostId: string,\n): number {\n const map = (orgMonth.exists ? orgMonth.get(AI_HOST_CREDITS_FIELD) : null) as\n | Record<string, unknown>\n | null\n if (map && map[hostId] !== undefined) {\n const value = Number(map[hostId])\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0\n }\n return roster.reduce((sum, entry) => sum + (entry.byHost[hostId] ?? 0), 0)\n}\n\nasync function handleGet(request: Request): Promise<Response> {\n const { query, headers } = await pluginRequestFromWeb(request)\n const params = (query ?? {}) as Record<string, unknown>\n const hostId = String(params['hostId'] ?? '').trim()\n const subjectUid = String(params['uid'] ?? '').trim()\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const orgId =\n String(params['orgId'] ?? '').trim() || (hostId ? ((await resolveOrgIdForHost(hostId)) ?? '') : '')\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const member = membership?.member ?? null\n if (!member && !decoded.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n decoded.staff || (await memberHasOrgPermission(orgId, member, 'billing.manage'))\n const canView =\n canManage || (await memberHasOrgPermission(orgId, member, 'billing.view'))\n const siteAdmin = isSiteAdminCollaborator(member, hostId)\n const allowed = subjectUid\n ? canView || subjectUid === decoded.uid || siteAdmin\n : hostId\n ? canView || siteAdmin\n : canView\n if (!allowed) return json({ error: 'billing.view required' }, 403)\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const month = aiUsageMonthKeys()[0]\n const [orgSnapshot, orgMonth, allotments, roster, hostDocs] = await Promise.all([\n orgRef.get(),\n orgRef.collection('assistUsage').doc(month).get(),\n listAiAllotments(firestore, orgId, subjectUid ? { uid: subjectUid } : hostId ? { hostId } : {}),\n readRosterMonths(firestore, orgRef, month),\n hostId\n ? firestore.collection('hosts').doc(hostId).get().then((doc) => (doc.exists ? [doc] : []))\n : firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get().then((snapshot) => snapshot.docs),\n ])\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n const byUid = new Map(roster.map((entry) => [entry.member.$id, entry]))\n\n const used = (allotment: AiAllotment): number => {\n const person = allotment.uid ? byUid.get(allotment.uid) : undefined\n switch (allotment.scope) {\n case 'member':\n return person?.credits ?? 0\n case 'collab':\n return allotment.hostId ? (person?.byHost[allotment.hostId] ?? 0) : 0\n case 'host':\n return allotment.hostId ? siteCredits(orgMonth, roster, allotment.hostId) : 0\n default:\n return 0\n }\n }\n const rows: AiAllotmentRowWire[] = allotments\n // A collaborator reading their own sees their own rows and nothing else.\n .filter((allotment) => canView || siteAdmin || allotment.uid === decoded.uid)\n .map((allotment) => ({\n subject: allotment.subject,\n scope: allotment.scope,\n uid: allotment.uid,\n hostId: allotment.hostId,\n credits: allotment.credits,\n mode: allotment.mode,\n models: allotment.models,\n used: used(allotment),\n }))\n\n const visibleMembers = roster.filter((entry) =>\n subjectUid\n ? entry.member.$id === subjectUid\n : hostId\n ? hostRoleFor(entry.member, hostId) !== null\n : canView,\n )\n const members: AiAllotmentsMemberWire[] = visibleMembers.map((entry) => ({\n uid: entry.member.$id,\n name: memberName(entry.member),\n email: typeof entry.member.email === 'string' ? entry.member.email : null,\n role: typeof entry.member.role === 'string' ? entry.member.role : null,\n orgWide: isOrgWideMember(entry.member),\n credits: entry.credits,\n byHost: entry.byHost,\n }))\n const hosts: AiAllotmentsHostWire[] = hostDocs.map((doc) => ({\n hostId: doc.id,\n name: String(doc.get('displayName') ?? '').trim() || String(doc.get('subdomain') ?? '').trim() || doc.id,\n credits: siteCredits(orgMonth, roster, doc.id),\n }))\n const provider = resolveAiProvider()\n const wire: AiAllotmentsWire = {\n orgId,\n month,\n pool: {\n used: assistCreditsFromUsd(Number(orgMonth.get('estCostUsd') ?? 0)),\n limit: resolveAssistCreditBudget(org as never),\n },\n allotments: rows,\n members,\n hosts,\n models: AI_MODEL_CATALOG.filter((entry) => !provider || entry.provider === provider.id).map(\n (entry) => ({ id: entry.id, label: entry.label, tier: entry.tier }),\n ),\n restrictionAvailable: AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never)),\n canEdit: {\n billing: canManage,\n collaborators: canManage || siteAdmin,\n },\n callerUid: decoded.uid,\n }\n return Response.json(wire, { status: 200, headers: { 'Cache-Control': 'no-store, private' } })\n}\n\ninterface ParsedWrite {\n subject: AiAllotmentSubject\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nconst CATALOG_IDS = new Set(AI_MODEL_CATALOG.map((entry) => entry.id))\n\n/** One entry of `set`, validated; a string names what is wrong. */\nexport function parseAllotmentWrite(raw: unknown): ParsedWrite | string {\n const entry = (raw ?? {}) as Record<string, unknown>\n const subject = parseAiAllotmentSubject(entry['subject'])\n if (!subject) return 'subject must be member:{uid}, collab:{hostId}:{uid}, host:{hostId} or org'\n let credits: number | null = null\n if (entry['credits'] !== null && entry['credits'] !== undefined) {\n credits = typeof entry['credits'] === 'number' ? aiAllotmentCredits(entry['credits']) : null\n if (credits === null || (entry['credits'] as number) > AI_ALLOTMENT_MAX_CREDITS) {\n return `credits must be a whole number from 1 to ${AI_ALLOTMENT_MAX_CREDITS.toLocaleString('en-US')}`\n }\n }\n const mode = entry['mode'] === undefined ? 'hard' : entry['mode']\n if (mode !== 'hard' && mode !== 'soft') return 'mode must be hard or soft'\n let models: string[] | null = null\n if (entry['models'] !== null && entry['models'] !== undefined) {\n if (!Array.isArray(entry['models'])) return 'models must be a list of model ids'\n const ids = [...new Set(entry['models'].map((id) => String(id ?? '').trim()))].filter(Boolean)\n if (ids.length > AI_ALLOTMENT_MAX_MODELS) return `name at most ${AI_ALLOTMENT_MAX_MODELS} models`\n const unknown = ids.find((id) => !CATALOG_IDS.has(id))\n if (unknown) return `${unknown} is not a model this platform offers`\n models = ids.length ? ids : null\n }\n if (subject.scope === 'org' && credits !== null) {\n return 'the org-wide restriction carries models, not credits'\n }\n if (subject.scope !== 'org' && credits === null && models === null) {\n return 'set credits or models, or remove the allotment'\n }\n return { subject, credits, mode, models }\n}\n\nasync function handlePost(request: Request): Promise<Response> {\n const { body, headers } = await pluginRequestFromWeb(request)\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const payload = (body ?? {}) as Record<string, unknown>\n const orgId = String(payload['orgId'] ?? '').trim()\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const rawSet = Array.isArray(payload['set']) ? payload['set'] : []\n const rawRemove = Array.isArray(payload['remove']) ? payload['remove'] : []\n const rawEverySite = payload['everySite'] ?? null\n if (!rawSet.length && !rawRemove.length && !rawEverySite) {\n return json({ error: 'Nothing to change' }, 400)\n }\n if (rawSet.length + rawRemove.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n\n const writes: ParsedWrite[] = []\n for (const raw of rawSet) {\n const parsed = parseAllotmentWrite(raw)\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n writes.push(parsed)\n }\n const removals: AiAllotmentSubject[] = []\n for (const raw of rawRemove) {\n const subject = parseAiAllotmentSubject(raw)\n if (!subject) return json({ error: `${String(raw)} is not an allotment subject` }, 400)\n removals.push(subject)\n }\n let everySite: Omit<ParsedWrite, 'subject'> | null = null\n if (rawEverySite) {\n const parsed = parseAllotmentWrite({ ...(rawEverySite as object), subject: 'host:every-site' })\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n everySite = { credits: parsed.credits, mode: parsed.mode, models: parsed.models }\n }\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const caller: Caller = {\n uid: decoded.uid,\n email: decoded.email ?? null,\n staff: decoded.staff,\n member: membership?.member ?? null,\n }\n if (!caller.member && !caller.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n caller.staff || (await memberHasOrgPermission(orgId, caller.member, 'billing.manage'))\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const orgSnapshot = await orgRef.get()\n if (!orgSnapshot.exists) return json({ error: 'Unknown organization' }, 404)\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n\n // Every subject the request touches, set or removed, is decided the same\n // way: the permission first, then that the person or site belongs here.\n const touched = [...writes.map((write) => write.subject), ...removals]\n if (everySite && !canManage) return json({ error: 'billing.manage required' }, 403)\n const memberDocs = new Map<string, AglynOrgMember | null>()\n const memberOf = async (uid: string): Promise<AglynOrgMember | null> => {\n if (!memberDocs.has(uid)) {\n const snapshot = await orgRef.collection('members').doc(uid).get()\n memberDocs.set(uid, snapshot.exists ? ({ $id: uid, ...snapshot.data() } as AglynOrgMember) : null)\n }\n return memberDocs.get(uid) ?? null\n }\n const hostNames = new Map<string, string>()\n for (const subject of touched) {\n if (subject.scope === 'collab') {\n const siteAdmin = isSiteAdminCollaborator(caller.member, subject.hostId ?? '')\n if (!canManage && !siteAdmin) return json({ error: 'billing.manage required' }, 403)\n if (!canManage && subject.uid === caller.uid) {\n return json({ error: 'You cannot change your own AI allotment', code: 'own_allotment' }, 403)\n }\n const target = await memberOf(subject.uid ?? '')\n if (!target || hostRoleFor(target, subject.hostId ?? '') === null) {\n return json({ error: 'That person is not a collaborator on that site' }, 404)\n }\n // A site's admin sets allotments for the site's COLLABORATORS: a team\n // member's AI is the organization's to bound, not one site's.\n if (!canManage && isOrgWideMember(target)) {\n return json({ error: 'billing.manage required' }, 403)\n }\n } else if (!canManage) {\n return json({ error: 'billing.manage required' }, 403)\n } else if (subject.scope === 'member') {\n if (!(await memberOf(subject.uid ?? ''))) {\n return json({ error: 'That person is not a member of this organization' }, 404)\n }\n } else if (subject.scope === 'host') {\n const host = await firestore.collection('hosts').doc(subject.hostId ?? '').get()\n if (!host.exists || host.get('orgId') !== orgId) {\n return json({ error: 'That site is not part of this organization' }, 404)\n }\n hostNames.set(host.id, String(host.get('displayName') ?? '').trim() || host.id)\n }\n }\n const restricting = writes.some((write) => write.subject.scope === 'org' && write.models)\n if (restricting && !AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never))) {\n return json(\n {\n error: 'Restricting models for the whole organization is available on Agency and Enterprise.',\n code: 'not_available',\n },\n 409,\n )\n }\n\n const set: AiAllotmentWrite[] = [...writes]\n if (everySite) {\n const sites = await firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get()\n for (const site of sites.docs) {\n hostNames.set(site.id, String(site.get('displayName') ?? '').trim() || site.id)\n const subject = parseAiAllotmentSubject(`host:${site.id}`)\n if (subject) set.push({ subject, ...everySite })\n }\n if (set.length + removals.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n }\n\n await applyAiAllotmentWrites(\n firestore,\n orgId,\n { set, remove: removals.map((subject) => subject.id) },\n caller.uid,\n )\n\n // A site allotment set in a month the site's map has no key for yet is\n // given the month so far from its people's own months, once.\n const month = aiUsageMonthKeys()[0]\n const siteIds = set\n .filter((write) => write.subject.scope === 'host' && write.credits !== null)\n .map((write) => write.subject.hostId as string)\n if (siteIds.length) {\n const roster = await readRosterMonths(firestore, orgRef, month)\n await seedAiHostMonthCredits(firestore, orgId, siteIds, month, (siteId) =>\n roster.reduce((sum, entry) => sum + (entry.byHost[siteId] ?? 0), 0),\n ).catch((error) => console.error('[ai/allotments] site month seed failed', orgId, error))\n }\n\n void addAdminAudit(firestore, {\n actorUid: caller.uid,\n actorEmail: caller.email,\n action: 'ai.allotments.set',\n target: `orgs/${orgId}/aiAllotments`,\n before: null,\n after: {\n set: set.map((write) => ({ subject: write.subject.id, credits: write.credits, mode: write.mode, models: write.models })),\n removed: removals.map((subject) => subject.id),\n },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n\n const actor = { uid: caller.uid, email: caller.email }\n const feedRow = (subject: AiAllotmentSubject) => ({\n type: subject.scope === 'host' ? ('host' as const) : subject.scope === 'org' ? ('org' as const) : ('member' as const),\n id: subject.scope === 'host' ? subject.hostId : subject.uid,\n name:\n subject.scope === 'host'\n ? hostNames.get(subject.hostId ?? '') ?? subject.hostId\n : subject.scope === 'org'\n ? 'Organization model restriction'\n : [\n memberDocs.get(subject.uid ?? '') ? memberName(memberDocs.get(subject.uid ?? '') as AglynOrgMember) : subject.uid,\n subject.scope === 'collab' ? `on ${subject.hostId}` : null,\n ]\n .filter(Boolean)\n .join(' '),\n })\n await Promise.all([\n ...set.map((write) =>\n logAiAllotmentChanged(orgId, actor, {\n subject: feedRow(write.subject),\n after: { credits: write.credits, mode: write.mode, models: write.models },\n }).catch(() => undefined),\n ),\n ...removals.map((subject) =>\n logAiAllotmentChanged(orgId, actor, { subject: feedRow(subject), after: null }).catch(\n () => undefined,\n ),\n ),\n ])\n\n return json({ ok: true, set: set.length, removed: removals.length }, 200)\n}\n\nasync function handler(request: Request): Promise<Response> {\n try {\n if (request.method === 'GET') return await handleGet(request)\n if (request.method === 'POST') return await handlePost(request)\n return json({ error: 'Method not allowed' }, 405)\n } catch (error) {\n // A refused credential is a 401, not a fault of ours (AGL-1993).\n const unauthenticated = invalidIdTokenResponse(error)\n if (unauthenticated) return unauthenticated\n console.error('[ai/allotments] failed', error)\n return json({ error: 'AI allotments unavailable' }, 500)\n }\n}\n\nexport { handler as GET, handler as POST }\n"],"names":["pluginRequestFromWeb","assistCreditsFromUsd","resolveAssistCreditBudget","hostRoleFor","isOrgWideMember","resolveEffectivePlan","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","memberHasOrgPermission","resolveOrgMembership","invalidIdTokenResponse","resolveOrgIdForHost","FieldValue","logAiAllotmentChanged","AI_ALLOTMENT_MAX_CREDITS","AI_ALLOTMENT_MAX_MODELS","aiAllotmentCredits","parseAiAllotmentSubject","aiUsageByUserMonthFrom","aiUsageMonthKeys","AI_MODEL_CATALOG","AI_MODEL_RESTRICTION_PLANS","resolveAiProvider","AI_HOST_CREDITS_FIELD","applyAiAllotmentWrites","listAiAllotments","seedAiHostMonthCredits","userAiUsageMonthRef","addAdminAudit","json","body","status","Response","MAX_WRITES","MAX_SITES","GET_ALL_CHUNK","authenticate","headers","authorization","idToken","startsWith","slice","length","undefined","error","decoded","app","auth","verifyIdToken","email_verified","uid","email","staff","isSiteAdminCollaborator","member","hostId","readRosterMonths","firestore","orgRef","month","roster","collection","get","members","docs","map","doc","$id","id","data","out","start","chunk","snapshots","getAll","forEach","snapshot","index","usage","exists","push","credits","byHost","memberName","String","displayName","trim","siteCredits","orgMonth","value","Number","isFinite","Math","floor","reduce","sum","entry","handleGet","request","params","orgSnapshot","query","subjectUid","orgId","membership","canManage","canView","siteAdmin","allowed","allotments","hostDocs","Promise","all","then","where","limit","org","byUid","Map","used","allotment","person","scope","rows","filter","subject","mode","models","visibleMembers","name","role","orgWide","hosts","provider","wire","pool","label","tier","restrictionAvailable","includes","canEdit","billing","collaborators","callerUid","CATALOG_IDS","Set","parseAllotmentWrite","raw","toLocaleString","Array","isArray","ids","Boolean","unknown","find","has","handlePost","payload","rawSet","rawRemove","rawEverySite","writes","parsed","code","removals","everySite","caller","touched","write","memberDocs","memberOf","set","hostNames","target","host","restricting","some","sites","site","remove","siteIds","siteId","catch","console","actorUid","actorEmail","action","before","after","removed","at","serverTimestamp","actor","feedRow","type","join","ok","handler","method","unauthenticated","GET","POST"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-allotments.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n assistCreditsFromUsd,\n resolveAssistCreditBudget,\n} from '../usage/assist-credits'\nimport { hostRoleFor, isOrgWideMember } from '@aglyn/aglyn/app-utils/organizations'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { invalidIdTokenResponse } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { logAiAllotmentChanged } from '../activity/ai-activity'\nimport {\n AI_ALLOTMENT_MAX_CREDITS,\n AI_ALLOTMENT_MAX_MODELS,\n aiAllotmentCredits,\n parseAiAllotmentSubject,\n type AiAllotment,\n type AiAllotmentMode,\n type AiAllotmentSubject,\n} from '../model/ai-allotments'\nimport { aiUsageByUserMonthFrom, aiUsageMonthKeys } from '../model/ai-usage-by-user'\nimport { AI_MODEL_CATALOG } from '../providers/catalog'\nimport { AI_MODEL_RESTRICTION_PLANS } from '../providers/model-choice'\nimport { resolveAiProvider } from '../providers/routing'\nimport {\n AI_HOST_CREDITS_FIELD,\n applyAiAllotmentWrites,\n listAiAllotments,\n seedAiHostMonthCredits,\n type AiAllotmentWrite,\n} from '../usage/ai-allotments'\nimport { userAiUsageMonthRef } from '../usage/ai-usage-by-user'\nimport {\n ASSIST_RETURNED_USD_FIELD,\n assistSpendAfterReturnsUsd,\n} from '../usage/assist-credit-returns'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n AiAllotmentsWire,\n} from '../usage/ai-usage-wire'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\n\n// lockdown-423: exempt — a spend control, the posture of ai/billing/overage\n// beside it: a billing-locked workspace must still be able to LOWER what its\n// people may spend, and a 423 would trap it spending.\n\n/**\n * AI ALLOTMENTS (AGL-2942) — read and set them.\n *\n * ## Who may read\n *\n * - The whole org's allotments (`orgId`): `billing.view`, the Usage page's\n * gate — every allotment is a figure about the pool it is part of.\n * - One site's (`hostId`): the same, or the site's admin when they are a\n * collaborator scoped to it — the collaborators card is theirs.\n * - One person's (`uid`): the same, or the person themselves.\n *\n * ## Who may write\n *\n * - A member's, a site's and the org-wide model restriction:\n * `billing.manage`. Each moves how much of the paid pool someone may\n * spend.\n * - A collaborator's on one site: `billing.manage`, or the admin of that\n * site when they are a collaborator scoped to it — never on their own\n * allotment, which they could otherwise raise.\n *\n * Staff pass, as at the other org routes. The org-wide restriction is set\n * only on a plan that offers it (`AI_MODEL_RESTRICTION_PLANS`); clearing it\n * is always allowed.\n *\n * ## One write, many subjects\n *\n * `POST { orgId, set, remove, everySite }`: `set` replaces allotments,\n * `remove` deletes them, and `everySite` sets the same allotment on every\n * site the org has — \"same for every client site\" — expanded here, from the\n * org's sites, rather than trusted from a client's list.\n */\n\nconst json = (body: unknown, status: number) => Response.json(body, { status })\n\n/** Batch writes a request may make; Firestore commits at most 500. */\nconst MAX_WRITES = 450\n\n/** Sites one `everySite` expands to. */\nconst MAX_SITES = 400\n\n/** Member months one `getAll` carries. */\nconst GET_ALL_CHUNK = 100\n\ninterface Caller {\n uid: string\n email: string | null\n staff: boolean\n member: AglynOrgMember | null\n}\n\nasync function authenticate(\n headers: Partial<Record<string, string>>,\n): Promise<Response | { decoded: { uid: string; email?: string; staff: boolean } }> {\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return json({ error: 'Unauthenticated' }, 401)\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n return { decoded: { uid: decoded.uid, email: decoded.email, staff: decoded['staff'] === true } }\n}\n\n/** A collaborator scoped to the site who holds its admin role there. */\nfunction isSiteAdminCollaborator(member: AglynOrgMember | null, hostId: string): boolean {\n if (!member || !hostId || isOrgWideMember(member)) return false\n return hostRoleFor(member, hostId) === 'admin'\n}\n\ninterface RosterMonth {\n member: AglynOrgMember\n credits: number\n byHost: Record<string, number>\n}\n\n/** The roster and each member's month, by path — no index, one getAll a hundred. */\nasync function readRosterMonths(\n firestore: FirebaseFirestore.Firestore,\n orgRef: FirebaseFirestore.DocumentReference,\n month: string,\n): Promise<RosterMonth[]> {\n const roster = await orgRef.collection('members').get()\n const members = roster.docs.map(\n (doc) => ({ $id: doc.id, ...doc.data() }) as AglynOrgMember,\n )\n const out: RosterMonth[] = []\n for (let start = 0; start < members.length; start += GET_ALL_CHUNK) {\n const chunk = members.slice(start, start + GET_ALL_CHUNK)\n const snapshots = chunk.length\n ? await firestore.getAll(\n ...chunk.map((member) => userAiUsageMonthRef(orgRef, member.$id, month)),\n )\n : []\n snapshots.forEach((snapshot, index) => {\n const member = chunk[index]\n const usage = aiUsageByUserMonthFrom(\n snapshot.exists ? (snapshot.data() as Record<string, unknown>) : null,\n member.$id,\n month,\n )\n out.push({ member, credits: usage.credits, byHost: usage.byHost })\n })\n }\n return out\n}\n\nconst memberName = (member: AglynOrgMember): string =>\n String(member.displayName ?? '').trim() || String(member.email ?? '').trim() || member.$id\n\n/** A site's credits this month: the org map, else its people's months summed. */\nfunction siteCredits(\n orgMonth: FirebaseFirestore.DocumentSnapshot,\n roster: readonly RosterMonth[],\n hostId: string,\n): number {\n const map = (orgMonth.exists ? orgMonth.get(AI_HOST_CREDITS_FIELD) : null) as\n | Record<string, unknown>\n | null\n if (map && map[hostId] !== undefined) {\n const value = Number(map[hostId])\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0\n }\n return roster.reduce((sum, entry) => sum + (entry.byHost[hostId] ?? 0), 0)\n}\n\nasync function handleGet(request: Request): Promise<Response> {\n const { query, headers } = await pluginRequestFromWeb(request)\n const params = (query ?? {}) as Record<string, unknown>\n const hostId = String(params['hostId'] ?? '').trim()\n const subjectUid = String(params['uid'] ?? '').trim()\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const orgId =\n String(params['orgId'] ?? '').trim() || (hostId ? ((await resolveOrgIdForHost(hostId)) ?? '') : '')\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const member = membership?.member ?? null\n if (!member && !decoded.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n decoded.staff || (await memberHasOrgPermission(orgId, member, 'billing.manage'))\n const canView =\n canManage || (await memberHasOrgPermission(orgId, member, 'billing.view'))\n const siteAdmin = isSiteAdminCollaborator(member, hostId)\n const allowed = subjectUid\n ? canView || subjectUid === decoded.uid || siteAdmin\n : hostId\n ? canView || siteAdmin\n : canView\n if (!allowed) return json({ error: 'billing.view required' }, 403)\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const month = aiUsageMonthKeys()[0]\n const [orgSnapshot, orgMonth, allotments, roster, hostDocs] = await Promise.all([\n orgRef.get(),\n orgRef.collection('assistUsage').doc(month).get(),\n listAiAllotments(firestore, orgId, subjectUid ? { uid: subjectUid } : hostId ? { hostId } : {}),\n readRosterMonths(firestore, orgRef, month),\n hostId\n ? firestore.collection('hosts').doc(hostId).get().then((doc) => (doc.exists ? [doc] : []))\n : firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get().then((snapshot) => snapshot.docs),\n ])\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n const byUid = new Map(roster.map((entry) => [entry.member.$id, entry]))\n\n const used = (allotment: AiAllotment): number => {\n const person = allotment.uid ? byUid.get(allotment.uid) : undefined\n switch (allotment.scope) {\n case 'member':\n return person?.credits ?? 0\n case 'collab':\n return allotment.hostId ? (person?.byHost[allotment.hostId] ?? 0) : 0\n case 'host':\n return allotment.hostId ? siteCredits(orgMonth, roster, allotment.hostId) : 0\n default:\n return 0\n }\n }\n const rows: AiAllotmentRowWire[] = allotments\n // A collaborator reading their own sees their own rows and nothing else.\n .filter((allotment) => canView || siteAdmin || allotment.uid === decoded.uid)\n .map((allotment) => ({\n subject: allotment.subject,\n scope: allotment.scope,\n uid: allotment.uid,\n hostId: allotment.hostId,\n credits: allotment.credits,\n mode: allotment.mode,\n models: allotment.models,\n used: used(allotment),\n }))\n\n const visibleMembers = roster.filter((entry) =>\n subjectUid\n ? entry.member.$id === subjectUid\n : hostId\n ? hostRoleFor(entry.member, hostId) !== null\n : canView,\n )\n const members: AiAllotmentsMemberWire[] = visibleMembers.map((entry) => ({\n uid: entry.member.$id,\n name: memberName(entry.member),\n email: typeof entry.member.email === 'string' ? entry.member.email : null,\n role: typeof entry.member.role === 'string' ? entry.member.role : null,\n orgWide: isOrgWideMember(entry.member),\n credits: entry.credits,\n byHost: entry.byHost,\n }))\n const hosts: AiAllotmentsHostWire[] = hostDocs.map((doc) => ({\n hostId: doc.id,\n name: String(doc.get('displayName') ?? '').trim() || String(doc.get('subdomain') ?? '').trim() || doc.id,\n credits: siteCredits(orgMonth, roster, doc.id),\n }))\n const provider = resolveAiProvider()\n const wire: AiAllotmentsWire = {\n orgId,\n month,\n pool: {\n used: assistCreditsFromUsd(\n assistSpendAfterReturnsUsd(\n orgMonth.get('estCostUsd'),\n orgMonth.get(ASSIST_RETURNED_USD_FIELD),\n ),\n ),\n limit: resolveAssistCreditBudget(org as never),\n },\n allotments: rows,\n members,\n hosts,\n models: AI_MODEL_CATALOG.filter((entry) => !provider || entry.provider === provider.id).map(\n (entry) => ({ id: entry.id, label: entry.label, tier: entry.tier }),\n ),\n restrictionAvailable: AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never)),\n canEdit: {\n billing: canManage,\n collaborators: canManage || siteAdmin,\n },\n callerUid: decoded.uid,\n }\n return Response.json(wire, { status: 200, headers: { 'Cache-Control': 'no-store, private' } })\n}\n\ninterface ParsedWrite {\n subject: AiAllotmentSubject\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nconst CATALOG_IDS = new Set(AI_MODEL_CATALOG.map((entry) => entry.id))\n\n/** One entry of `set`, validated; a string names what is wrong. */\nexport function parseAllotmentWrite(raw: unknown): ParsedWrite | string {\n const entry = (raw ?? {}) as Record<string, unknown>\n const subject = parseAiAllotmentSubject(entry['subject'])\n if (!subject) return 'subject must be member:{uid}, collab:{hostId}:{uid}, host:{hostId} or org'\n let credits: number | null = null\n if (entry['credits'] !== null && entry['credits'] !== undefined) {\n credits = typeof entry['credits'] === 'number' ? aiAllotmentCredits(entry['credits']) : null\n if (credits === null || (entry['credits'] as number) > AI_ALLOTMENT_MAX_CREDITS) {\n return `credits must be a whole number from 1 to ${AI_ALLOTMENT_MAX_CREDITS.toLocaleString('en-US')}`\n }\n }\n const mode = entry['mode'] === undefined ? 'hard' : entry['mode']\n if (mode !== 'hard' && mode !== 'soft') return 'mode must be hard or soft'\n let models: string[] | null = null\n if (entry['models'] !== null && entry['models'] !== undefined) {\n if (!Array.isArray(entry['models'])) return 'models must be a list of model ids'\n const ids = [...new Set(entry['models'].map((id) => String(id ?? '').trim()))].filter(Boolean)\n if (ids.length > AI_ALLOTMENT_MAX_MODELS) return `name at most ${AI_ALLOTMENT_MAX_MODELS} models`\n const unknown = ids.find((id) => !CATALOG_IDS.has(id))\n if (unknown) return `${unknown} is not a model this platform offers`\n models = ids.length ? ids : null\n }\n if (subject.scope === 'org' && credits !== null) {\n return 'the org-wide restriction carries models, not credits'\n }\n if (subject.scope !== 'org' && credits === null && models === null) {\n return 'set credits or models, or remove the allotment'\n }\n return { subject, credits, mode, models }\n}\n\nasync function handlePost(request: Request): Promise<Response> {\n const { body, headers } = await pluginRequestFromWeb(request)\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const payload = (body ?? {}) as Record<string, unknown>\n const orgId = String(payload['orgId'] ?? '').trim()\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const rawSet = Array.isArray(payload['set']) ? payload['set'] : []\n const rawRemove = Array.isArray(payload['remove']) ? payload['remove'] : []\n const rawEverySite = payload['everySite'] ?? null\n if (!rawSet.length && !rawRemove.length && !rawEverySite) {\n return json({ error: 'Nothing to change' }, 400)\n }\n if (rawSet.length + rawRemove.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n\n const writes: ParsedWrite[] = []\n for (const raw of rawSet) {\n const parsed = parseAllotmentWrite(raw)\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n writes.push(parsed)\n }\n const removals: AiAllotmentSubject[] = []\n for (const raw of rawRemove) {\n const subject = parseAiAllotmentSubject(raw)\n if (!subject) return json({ error: `${String(raw)} is not an allotment subject` }, 400)\n removals.push(subject)\n }\n let everySite: Omit<ParsedWrite, 'subject'> | null = null\n if (rawEverySite) {\n const parsed = parseAllotmentWrite({ ...(rawEverySite as object), subject: 'host:every-site' })\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n everySite = { credits: parsed.credits, mode: parsed.mode, models: parsed.models }\n }\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const caller: Caller = {\n uid: decoded.uid,\n email: decoded.email ?? null,\n staff: decoded.staff,\n member: membership?.member ?? null,\n }\n if (!caller.member && !caller.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n caller.staff || (await memberHasOrgPermission(orgId, caller.member, 'billing.manage'))\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const orgSnapshot = await orgRef.get()\n if (!orgSnapshot.exists) return json({ error: 'Unknown organization' }, 404)\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n\n // Every subject the request touches, set or removed, is decided the same\n // way: the permission first, then that the person or site belongs here.\n const touched = [...writes.map((write) => write.subject), ...removals]\n if (everySite && !canManage) return json({ error: 'billing.manage required' }, 403)\n const memberDocs = new Map<string, AglynOrgMember | null>()\n const memberOf = async (uid: string): Promise<AglynOrgMember | null> => {\n if (!memberDocs.has(uid)) {\n const snapshot = await orgRef.collection('members').doc(uid).get()\n memberDocs.set(uid, snapshot.exists ? ({ $id: uid, ...snapshot.data() } as AglynOrgMember) : null)\n }\n return memberDocs.get(uid) ?? null\n }\n const hostNames = new Map<string, string>()\n for (const subject of touched) {\n if (subject.scope === 'collab') {\n const siteAdmin = isSiteAdminCollaborator(caller.member, subject.hostId ?? '')\n if (!canManage && !siteAdmin) return json({ error: 'billing.manage required' }, 403)\n if (!canManage && subject.uid === caller.uid) {\n return json({ error: 'You cannot change your own AI allotment', code: 'own_allotment' }, 403)\n }\n const target = await memberOf(subject.uid ?? '')\n if (!target || hostRoleFor(target, subject.hostId ?? '') === null) {\n return json({ error: 'That person is not a collaborator on that site' }, 404)\n }\n // A site's admin sets allotments for the site's COLLABORATORS: a team\n // member's AI is the organization's to bound, not one site's.\n if (!canManage && isOrgWideMember(target)) {\n return json({ error: 'billing.manage required' }, 403)\n }\n } else if (!canManage) {\n return json({ error: 'billing.manage required' }, 403)\n } else if (subject.scope === 'member') {\n if (!(await memberOf(subject.uid ?? ''))) {\n return json({ error: 'That person is not a member of this organization' }, 404)\n }\n } else if (subject.scope === 'host') {\n const host = await firestore.collection('hosts').doc(subject.hostId ?? '').get()\n if (!host.exists || host.get('orgId') !== orgId) {\n return json({ error: 'That site is not part of this organization' }, 404)\n }\n hostNames.set(host.id, String(host.get('displayName') ?? '').trim() || host.id)\n }\n }\n const restricting = writes.some((write) => write.subject.scope === 'org' && write.models)\n if (restricting && !AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never))) {\n return json(\n {\n error: 'Restricting models for the whole organization is available on Agency and Enterprise.',\n code: 'not_available',\n },\n 409,\n )\n }\n\n const set: AiAllotmentWrite[] = [...writes]\n if (everySite) {\n const sites = await firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get()\n for (const site of sites.docs) {\n hostNames.set(site.id, String(site.get('displayName') ?? '').trim() || site.id)\n const subject = parseAiAllotmentSubject(`host:${site.id}`)\n if (subject) set.push({ subject, ...everySite })\n }\n if (set.length + removals.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n }\n\n await applyAiAllotmentWrites(\n firestore,\n orgId,\n { set, remove: removals.map((subject) => subject.id) },\n caller.uid,\n )\n\n // A site allotment set in a month the site's map has no key for yet is\n // given the month so far from its people's own months, once.\n const month = aiUsageMonthKeys()[0]\n const siteIds = set\n .filter((write) => write.subject.scope === 'host' && write.credits !== null)\n .map((write) => write.subject.hostId as string)\n if (siteIds.length) {\n const roster = await readRosterMonths(firestore, orgRef, month)\n await seedAiHostMonthCredits(firestore, orgId, siteIds, month, (siteId) =>\n roster.reduce((sum, entry) => sum + (entry.byHost[siteId] ?? 0), 0),\n ).catch((error) => console.error('[ai/allotments] site month seed failed', orgId, error))\n }\n\n void addAdminAudit(firestore, {\n actorUid: caller.uid,\n actorEmail: caller.email,\n action: 'ai.allotments.set',\n target: `orgs/${orgId}/aiAllotments`,\n before: null,\n after: {\n set: set.map((write) => ({ subject: write.subject.id, credits: write.credits, mode: write.mode, models: write.models })),\n removed: removals.map((subject) => subject.id),\n },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n\n const actor = { uid: caller.uid, email: caller.email }\n const feedRow = (subject: AiAllotmentSubject) => ({\n type: subject.scope === 'host' ? ('host' as const) : subject.scope === 'org' ? ('org' as const) : ('member' as const),\n id: subject.scope === 'host' ? subject.hostId : subject.uid,\n name:\n subject.scope === 'host'\n ? hostNames.get(subject.hostId ?? '') ?? subject.hostId\n : subject.scope === 'org'\n ? 'Organization model restriction'\n : [\n memberDocs.get(subject.uid ?? '') ? memberName(memberDocs.get(subject.uid ?? '') as AglynOrgMember) : subject.uid,\n subject.scope === 'collab' ? `on ${subject.hostId}` : null,\n ]\n .filter(Boolean)\n .join(' '),\n })\n await Promise.all([\n ...set.map((write) =>\n logAiAllotmentChanged(orgId, actor, {\n subject: feedRow(write.subject),\n after: { credits: write.credits, mode: write.mode, models: write.models },\n }).catch(() => undefined),\n ),\n ...removals.map((subject) =>\n logAiAllotmentChanged(orgId, actor, { subject: feedRow(subject), after: null }).catch(\n () => undefined,\n ),\n ),\n ])\n\n return json({ ok: true, set: set.length, removed: removals.length }, 200)\n}\n\nasync function handler(request: Request): Promise<Response> {\n try {\n if (request.method === 'GET') return await handleGet(request)\n if (request.method === 'POST') return await handlePost(request)\n return json({ error: 'Method not allowed' }, 405)\n } catch (error) {\n // A refused credential is a 401, not a fault of ours (AGL-1993).\n const unauthenticated = invalidIdTokenResponse(error)\n if (unauthenticated) return unauthenticated\n console.error('[ai/allotments] failed', error)\n return json({ error: 'AI allotments unavailable' }, 500)\n }\n}\n\nexport { handler as GET, handler as POST }\n"],"names":["pluginRequestFromWeb","assistCreditsFromUsd","resolveAssistCreditBudget","hostRoleFor","isOrgWideMember","resolveEffectivePlan","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","memberHasOrgPermission","resolveOrgMembership","invalidIdTokenResponse","resolveOrgIdForHost","FieldValue","logAiAllotmentChanged","AI_ALLOTMENT_MAX_CREDITS","AI_ALLOTMENT_MAX_MODELS","aiAllotmentCredits","parseAiAllotmentSubject","aiUsageByUserMonthFrom","aiUsageMonthKeys","AI_MODEL_CATALOG","AI_MODEL_RESTRICTION_PLANS","resolveAiProvider","AI_HOST_CREDITS_FIELD","applyAiAllotmentWrites","listAiAllotments","seedAiHostMonthCredits","userAiUsageMonthRef","ASSIST_RETURNED_USD_FIELD","assistSpendAfterReturnsUsd","addAdminAudit","json","body","status","Response","MAX_WRITES","MAX_SITES","GET_ALL_CHUNK","authenticate","headers","authorization","idToken","startsWith","slice","length","undefined","error","decoded","app","auth","verifyIdToken","email_verified","uid","email","staff","isSiteAdminCollaborator","member","hostId","readRosterMonths","firestore","orgRef","month","roster","collection","get","members","docs","map","doc","$id","id","data","out","start","chunk","snapshots","getAll","forEach","snapshot","index","usage","exists","push","credits","byHost","memberName","String","displayName","trim","siteCredits","orgMonth","value","Number","isFinite","Math","floor","reduce","sum","entry","handleGet","request","params","orgSnapshot","query","subjectUid","orgId","membership","canManage","canView","siteAdmin","allowed","allotments","hostDocs","Promise","all","then","where","limit","org","byUid","Map","used","allotment","person","scope","rows","filter","subject","mode","models","visibleMembers","name","role","orgWide","hosts","provider","wire","pool","label","tier","restrictionAvailable","includes","canEdit","billing","collaborators","callerUid","CATALOG_IDS","Set","parseAllotmentWrite","raw","toLocaleString","Array","isArray","ids","Boolean","unknown","find","has","handlePost","payload","rawSet","rawRemove","rawEverySite","writes","parsed","code","removals","everySite","caller","touched","write","memberDocs","memberOf","set","hostNames","target","host","restricting","some","sites","site","remove","siteIds","siteId","catch","console","actorUid","actorEmail","action","before","after","removed","at","serverTimestamp","actor","feedRow","type","join","ok","handler","method","unauthenticated","GET","POST"],"mappings":"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"}
@@ -18,6 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  // The POST climbs `aiGateLadder`, whose lockdown rung is the verdict; the
19
19
  // GET climbs `ai-jobs-gate.ts`, which carries the same rung.
20
20
  import { randomUUID } from "crypto";
21
+ import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
21
22
  import { AI_JOB_KINDS } from "../model/ai-jobs.types.js";
22
23
  // By their own entry points rather than the barrel (AGL-2903): the specs
23
24
  // stub the barrel closed-world, and the ladder, the machine and the runtime
@@ -25,10 +26,12 @@ import { AI_JOB_KINDS } from "../model/ai-jobs.types.js";
25
26
  import { aiGateLadder } from "../runtime/ai-gate.js";
26
27
  import { aiJobAdmissionRefusal, aiJobSiteRefusal } from "../jobs/ai-job-admission.js";
27
28
  import { AI_JOB_BRIEF_MAX_CHARS } from "../jobs/ai-job-text-step.js";
29
+ import { aiJobAdmittedInputs } from "../jobs/ai-job-auto-confirm.js";
28
30
  import { AI_JOB_INLINE_BUDGET_MS, aiJobNextStepMinimumMs, aiJobSummary, createAiJob, listAiJobs, runAiJobStep } from "../jobs/ai-jobs.js";
29
31
  import { releaseAssistMessage } from "../usage/assist-usage.js";
30
32
  import { aiUsageMeter } from "../usage/ai-usage-meter.js";
31
33
  import { aiJobsGate } from "./ai-jobs-gate.js";
34
+ import { AI_JOB_ACTIVE_STATUSES } from "../model/ai-job-activity.js";
32
35
  /**
33
36
  * AI generation jobs: create and list (AGL-2904).
34
37
  *
@@ -197,12 +200,19 @@ export async function POST(request) {
197
200
  let created;
198
201
  try {
199
202
  var _gate_decoded_email1;
203
+ // The guided start's own plan confirmation (AGL-3594), kept only for a
204
+ // site job on a site that has published nothing of the owner's yet.
205
+ const inputs = await aiJobAdmittedInputs(gate.firestore, {
206
+ kind: parsed.kind,
207
+ hostId: parsed.hostId,
208
+ inputs: parsed.inputs
209
+ });
200
210
  created = await createAiJob(gate.firestore, {
201
211
  orgId: gate.orgId,
202
212
  hostId: parsed.hostId,
203
213
  kind: parsed.kind,
204
214
  brief: parsed.brief,
205
- inputs: parsed.inputs,
215
+ inputs,
206
216
  model: parsed.model,
207
217
  createdBy: gate.uid,
208
218
  createdByEmail: (_gate_decoded_email1 = gate.decoded.email) != null ? _gate_decoded_email1 : null
@@ -285,15 +295,23 @@ export async function GET(request) {
285
295
  if (gate instanceof Response) return gate;
286
296
  const rawStatus = (_url_searchParams_get1 = url.searchParams.get('status')) != null ? _url_searchParams_get1 : '';
287
297
  const status = AI_JOB_STATUSES.includes(rawStatus) ? rawStatus : undefined;
298
+ // `active` (AGL-3593): every job not yet settled, which is what the
299
+ // console's top-bar indicator and the Assist launcher count.
300
+ const active = rawStatus === 'active';
288
301
  const limit = Number((_url_searchParams_get2 = url.searchParams.get('limit')) != null ? _url_searchParams_get2 : '');
289
302
  const now = new Date();
290
303
  const jobs = await listAiJobs(gate.firestore, gate.orgId, _extends({}, status ? {
291
304
  status
305
+ } : {}, active ? {
306
+ statuses: AI_JOB_ACTIVE_STATUSES
292
307
  } : {}, Number.isFinite(limit) && limit > 0 ? {
293
308
  limit
294
309
  } : {}));
295
310
  return Response.json({
296
- jobs: jobs.map((job)=>aiJobSummary(job, now))
311
+ jobs: jobs.map((job)=>aiJobSummary(job, now)),
312
+ // Whether the workspace spends the Free taste (AGL-3594), so the guided
313
+ // start offers the Free page band from the request it already makes.
314
+ freeTaste: resolveEffectivePlan(gate.org) === 'free'
297
315
  }, {
298
316
  status: 200,
299
317
  headers: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-jobs-route.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// lockdown-423: via libs/plugins/ai/src/lib/runtime/ai-gate.ts\n// The POST climbs `aiGateLadder`, whose lockdown rung is the verdict; the\n// GET climbs `ai-jobs-gate.ts`, which carries the same rung.\n\nimport { randomUUID } from 'crypto'\nimport {\n AI_JOB_KINDS,\n type AiJobKind,\n type AiJobStatus,\n} from '../model/ai-jobs.types'\n// By their own entry points rather than the barrel (AGL-2903): the specs\n// stub the barrel closed-world, and the ladder, the machine and the runtime\n// are the things under test here, not things to be stubbed away.\nimport { aiGateLadder } from '../runtime/ai-gate'\nimport { aiJobAdmissionRefusal, aiJobSiteRefusal } from '../jobs/ai-job-admission'\nimport { AI_JOB_BRIEF_MAX_CHARS } from '../jobs/ai-job-text-step'\nimport {\n AI_JOB_INLINE_BUDGET_MS,\n aiJobNextStepMinimumMs,\n aiJobSummary,\n createAiJob,\n listAiJobs,\n runAiJobStep,\n} from '../jobs/ai-jobs'\nimport { releaseAssistMessage } from '../usage/assist-usage'\nimport { aiUsageMeter } from '../usage/ai-usage-meter'\nimport { aiJobsGate } from './ai-jobs-gate'\n\n/**\n * AI generation jobs: create and list (AGL-2904).\n *\n * `POST` is a door that spends, so it climbs the whole ladder (AGL-2903) —\n * `aiGenerative`, `release_ai_generative`, the `ai-generate` switch, a\n * per-uid window, and a reservation — and then creates the job and runs\n * its first step INLINE under `AI_JOB_INLINE_BUDGET_MS`. A step that\n * finishes in time answers with the job `done` (or `queued` behind its\n * next step); one that does not is aborted, its reservation handed back,\n * and the job answers `queued` for the beat to resume. Either way the\n * caller gets the job document, and the events route is how it watches\n * from there.\n *\n * `GET` lists the org's jobs, newest first, through the read gate: the\n * same rungs up to the lockdown verdict, no reservation.\n *\n * The request must NAME the org it is metered against (AGL-1934). There\n * is no fallback to \"the caller's first org\".\n */\n\nconst AI_JOBS_RATE_LIMIT = { key: 'ai-jobs', limit: 10, windowMs: 60_000 }\n\n/** Kind-specific inputs, bounded: a runner reads scalars, not a document. */\nconst MAX_INPUTS_JSON_CHARS = 8_000\n\n/** The admitted statuses for the list filter, as the union spells them. */\nconst AI_JOB_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport interface CreateAiJobBody {\n orgId: string\n hostId: string | null\n kind: AiJobKind\n brief: string\n inputs: Record<string, string | number | boolean>\n /** A catalog model the creator picked (AGL-2942), or `null` for Auto. */\n model: string | null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,100}$/\n\n/** The longest model id a body may carry; the catalog's ids are far shorter. */\nconst MAX_MODEL_CHARS = 100\n\n/** Validate + clamp the request body; a string names what is wrong. */\nexport function parseCreateAiJobBody(payload: unknown): CreateAiJobBody | string {\n const body = (payload ?? {}) as Record<string, unknown>\n const orgId = String(body.orgId ?? '').trim()\n if (!orgId) return 'Open a workspace before using this feature'\n const kind = String(body.kind ?? '')\n if (!(AI_JOB_KINDS as readonly string[]).includes(kind)) {\n return `kind must be one of ${AI_JOB_KINDS.join(', ')}`\n }\n const brief = String(body.brief ?? '').trim()\n if (!brief) return 'Write a brief for the job'\n if (brief.length > AI_JOB_BRIEF_MAX_CHARS) {\n return `Keep the brief under ${AI_JOB_BRIEF_MAX_CHARS.toLocaleString('en-US')} characters`\n }\n const rawHost = body.hostId\n const hostId =\n rawHost === undefined || rawHost === null || rawHost === ''\n ? null\n : typeof rawHost === 'string' && ID_CHARS.test(rawHost)\n ? rawHost\n : undefined\n if (hostId === undefined) return 'hostId is not a site id'\n // A theme is fields on one site's document, so a theme job that names no\n // site has nothing to propose a change to (AGL-2938).\n if (kind === 'theme' && !hostId) return 'Open a site before changing its theme'\n const rawInputs = body.inputs\n const inputs: Record<string, string | number | boolean> = {}\n if (rawInputs !== undefined && rawInputs !== null) {\n if (typeof rawInputs !== 'object' || Array.isArray(rawInputs)) {\n return 'inputs must be an object'\n }\n for (const [key, value] of Object.entries(rawInputs as Record<string, unknown>)) {\n if (\n typeof value !== 'string' &&\n typeof value !== 'number' &&\n typeof value !== 'boolean'\n ) {\n return `inputs.${key} must be a string, number or boolean`\n }\n inputs[key] = value\n }\n if (JSON.stringify(inputs).length > MAX_INPUTS_JSON_CHARS) {\n return 'inputs are too large'\n }\n }\n const rawModel = typeof body.model === 'string' ? body.model.trim() : ''\n if (rawModel.length > MAX_MODEL_CHARS) return 'model is not a model id'\n // Validated against the plan and the allotment allowlists when each step\n // runs, not here: the allowlists are read inside the step's reservation.\n const model = rawModel && rawModel !== 'auto' ? rawModel : null\n return { orgId, hostId, kind: kind as AiJobKind, brief, inputs, model }\n}\n\nexport async function POST(request: Request): Promise<Response> {\n let payload: unknown\n try {\n payload = await request.json()\n } catch {\n payload = null\n }\n const parsed = parseCreateAiJobBody(payload)\n // The org is parsed BEFORE the ladder so a request that named none is\n // refused with the ladder's 400 rather than a shape error; every other\n // shape fault waits until the caller has proven who they are.\n const orgId = typeof parsed === 'string' ? String((payload as Record<string, unknown> | null)?.orgId ?? '') : parsed.orgId\n // The site rides along for the same reason: a collaborator's `ai.generate`\n // is decided per site (AGL-2927), so the ladder needs it before it can say\n // whether this member may generate here at all.\n const hostId = typeof parsed === 'string' ? null : parsed.hostId\n const gate = await aiGateLadder(\n { request, orgId, hostId },\n {\n feature: 'aiGenerative',\n releaseFlag: 'release_ai_generative',\n lockdownFeature: 'ai-generate',\n permission: 'ai.generate',\n rateLimit: AI_JOBS_RATE_LIMIT,\n },\n )\n if (gate instanceof Response) return gate\n if (typeof parsed === 'string') {\n // Admitted, reserved, and then found to have sent nothing runnable: the\n // reservation goes back, because no token was spent.\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n return Response.json({ error: parsed }, { status: 400 })\n }\n // What only this kind can say about the request (AGL-2909): a site it\n // needs, inputs it reads, an allowance its draft counts against. Asked\n // before the job exists, so a refusal spends nothing.\n let refusal: Awaited<ReturnType<typeof aiJobAdmissionRefusal>>\n try {\n // A site that switched AI off first (AGL-3028), then the kind's own check.\n refusal =\n (await aiJobSiteRefusal({\n firestore: gate.firestore,\n org: gate.org,\n hostId: parsed.hostId,\n })) ??\n (await aiJobAdmissionRefusal(parsed.kind, {\n firestore: gate.firestore,\n orgId: gate.orgId,\n hostId: parsed.hostId,\n inputs: parsed.inputs,\n org: gate.org,\n uid: gate.uid,\n }))\n } catch (error) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n console.error('ai job admission failed', { orgId: gate.orgId, kind: parsed.kind, error })\n return Response.json({ error: 'The AI job could not be created' }, { status: 500 })\n }\n if (refusal) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n return Response.json({ error: refusal.error }, { status: refusal.status })\n }\n\n const now = new Date()\n let created: Awaited<ReturnType<typeof createAiJob>>\n try {\n created = await createAiJob(\n gate.firestore,\n {\n orgId: gate.orgId,\n hostId: parsed.hostId,\n kind: parsed.kind,\n brief: parsed.brief,\n inputs: parsed.inputs,\n model: parsed.model,\n createdBy: gate.uid,\n createdByEmail: gate.decoded.email ?? null,\n },\n now,\n )\n } catch (error) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n console.error('ai job create failed', { orgId: gate.orgId, error })\n return Response.json({ error: 'The AI job could not be created' }, { status: 500 })\n }\n const jobId = created.$id\n\n // A first step that needs more time than this request has (AGL-2907) is left\n // queued for the beat, which starts it with a budget of its own: a provider\n // call this request's timeout cut off would be billed upstream and metered\n // nowhere. Nothing ran, so the reservation goes back.\n if (aiJobNextStepMinimumMs(created) > AI_JOB_INLINE_BUDGET_MS) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n return Response.json(\n {\n job: aiJobSummary(created, now),\n meter: aiUsageMeter(gate.reservation, { lastCredits: null }),\n },\n { status: 200 },\n )\n }\n\n // The first step, inline, on the reservation the ladder already holds.\n // The runner hands that reservation back itself if the provider is never\n // reached, and re-queues the step when the budget ends first.\n const run = await runAiJobStep(gate.firestore, gate.orgId, jobId, {\n owner: `route:${randomUUID()}`,\n now,\n reservation: gate.reservation,\n org: gate.org,\n signal: AbortSignal.timeout(AI_JOB_INLINE_BUDGET_MS),\n // The caller is on the request, so what the inline step produces is\n // attributed with their address; the beat's steps carry the uid alone.\n actor: { uid: gate.uid, email: gate.decoded.email ?? null },\n // The workspace's AI pause lets staff through, as the ladder above did.\n staff: gate.staff,\n })\n if (run.outcome === 'not-claimable') {\n // A job created a moment ago has a claimable first step; anything else\n // is a fault in the machine, and the reservation must not be stranded.\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n console.error('ai job first step not claimable', { orgId: gate.orgId, jobId })\n return Response.json({ error: 'The AI job could not be started' }, { status: 500 })\n }\n return Response.json(\n {\n job: aiJobSummary(run.job),\n // The usage strip's envelope (AGL-2942): the reservation the ladder\n // took, with what the inline step spent.\n meter: aiUsageMeter(gate.reservation, {\n lastCredits: run.job.creditsSpent > 0 ? run.job.creditsSpent : null,\n }),\n },\n { status: 200 },\n )\n}\n\nexport async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url)\n const gate = await aiJobsGate(request, url.searchParams.get('orgId') ?? '')\n if (gate instanceof Response) return gate\n const rawStatus = url.searchParams.get('status') ?? ''\n const status = (AI_JOB_STATUSES as readonly string[]).includes(rawStatus)\n ? (rawStatus as AiJobStatus)\n : undefined\n const limit = Number(url.searchParams.get('limit') ?? '')\n const now = new Date()\n const jobs = await listAiJobs(gate.firestore, gate.orgId, {\n ...(status ? { status } : {}),\n ...(Number.isFinite(limit) && limit > 0 ? { limit } : {}),\n })\n return Response.json(\n { jobs: jobs.map((job) => aiJobSummary(job, now)) },\n { status: 200, headers: { 'Cache-Control': 'no-store' } },\n )\n}\n\nexport const dynamic = 'force-dynamic'\nexport const maxDuration = 60\n"],"names":["randomUUID","AI_JOB_KINDS","aiGateLadder","aiJobAdmissionRefusal","aiJobSiteRefusal","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_INLINE_BUDGET_MS","aiJobNextStepMinimumMs","aiJobSummary","createAiJob","listAiJobs","runAiJobStep","releaseAssistMessage","aiUsageMeter","aiJobsGate","AI_JOBS_RATE_LIMIT","key","limit","windowMs","MAX_INPUTS_JSON_CHARS","AI_JOB_STATUSES","ID_CHARS","MAX_MODEL_CHARS","parseCreateAiJobBody","payload","body","orgId","String","trim","kind","includes","join","brief","length","toLocaleString","rawHost","hostId","undefined","test","rawInputs","inputs","Array","isArray","value","Object","entries","JSON","stringify","rawModel","model","POST","request","gate","json","parsed","feature","releaseFlag","lockdownFeature","permission","rateLimit","Response","firestore","reservation","catch","error","status","refusal","org","uid","console","now","Date","created","createdBy","createdByEmail","decoded","email","jobId","$id","job","meter","lastCredits","run","owner","signal","AbortSignal","timeout","actor","staff","outcome","creditsSpent","GET","url","URL","searchParams","get","rawStatus","Number","jobs","isFinite","map","headers","dynamic","maxDuration"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-jobs-route.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// lockdown-423: via libs/plugins/ai/src/lib/runtime/ai-gate.ts\n// The POST climbs `aiGateLadder`, whose lockdown rung is the verdict; the\n// GET climbs `ai-jobs-gate.ts`, which carries the same rung.\n\nimport { randomUUID } from 'crypto'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n AI_JOB_KINDS,\n type AiJobKind,\n type AiJobStatus,\n} from '../model/ai-jobs.types'\n// By their own entry points rather than the barrel (AGL-2903): the specs\n// stub the barrel closed-world, and the ladder, the machine and the runtime\n// are the things under test here, not things to be stubbed away.\nimport { aiGateLadder } from '../runtime/ai-gate'\nimport { aiJobAdmissionRefusal, aiJobSiteRefusal } from '../jobs/ai-job-admission'\nimport { AI_JOB_BRIEF_MAX_CHARS } from '../jobs/ai-job-text-step'\nimport { aiJobAdmittedInputs } from '../jobs/ai-job-auto-confirm'\nimport {\n AI_JOB_INLINE_BUDGET_MS,\n aiJobNextStepMinimumMs,\n aiJobSummary,\n createAiJob,\n listAiJobs,\n runAiJobStep,\n} from '../jobs/ai-jobs'\nimport { releaseAssistMessage } from '../usage/assist-usage'\nimport { aiUsageMeter } from '../usage/ai-usage-meter'\nimport { aiJobsGate } from './ai-jobs-gate'\nimport { AI_JOB_ACTIVE_STATUSES } from '../model/ai-job-activity'\n\n/**\n * AI generation jobs: create and list (AGL-2904).\n *\n * `POST` is a door that spends, so it climbs the whole ladder (AGL-2903) —\n * `aiGenerative`, `release_ai_generative`, the `ai-generate` switch, a\n * per-uid window, and a reservation — and then creates the job and runs\n * its first step INLINE under `AI_JOB_INLINE_BUDGET_MS`. A step that\n * finishes in time answers with the job `done` (or `queued` behind its\n * next step); one that does not is aborted, its reservation handed back,\n * and the job answers `queued` for the beat to resume. Either way the\n * caller gets the job document, and the events route is how it watches\n * from there.\n *\n * `GET` lists the org's jobs, newest first, through the read gate: the\n * same rungs up to the lockdown verdict, no reservation.\n *\n * The request must NAME the org it is metered against (AGL-1934). There\n * is no fallback to \"the caller's first org\".\n */\n\nconst AI_JOBS_RATE_LIMIT = { key: 'ai-jobs', limit: 10, windowMs: 60_000 }\n\n/** Kind-specific inputs, bounded: a runner reads scalars, not a document. */\nconst MAX_INPUTS_JSON_CHARS = 8_000\n\n/** The admitted statuses for the list filter, as the union spells them. */\nconst AI_JOB_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport interface CreateAiJobBody {\n orgId: string\n hostId: string | null\n kind: AiJobKind\n brief: string\n inputs: Record<string, string | number | boolean>\n /** A catalog model the creator picked (AGL-2942), or `null` for Auto. */\n model: string | null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,100}$/\n\n/** The longest model id a body may carry; the catalog's ids are far shorter. */\nconst MAX_MODEL_CHARS = 100\n\n/** Validate + clamp the request body; a string names what is wrong. */\nexport function parseCreateAiJobBody(payload: unknown): CreateAiJobBody | string {\n const body = (payload ?? {}) as Record<string, unknown>\n const orgId = String(body.orgId ?? '').trim()\n if (!orgId) return 'Open a workspace before using this feature'\n const kind = String(body.kind ?? '')\n if (!(AI_JOB_KINDS as readonly string[]).includes(kind)) {\n return `kind must be one of ${AI_JOB_KINDS.join(', ')}`\n }\n const brief = String(body.brief ?? '').trim()\n if (!brief) return 'Write a brief for the job'\n if (brief.length > AI_JOB_BRIEF_MAX_CHARS) {\n return `Keep the brief under ${AI_JOB_BRIEF_MAX_CHARS.toLocaleString('en-US')} characters`\n }\n const rawHost = body.hostId\n const hostId =\n rawHost === undefined || rawHost === null || rawHost === ''\n ? null\n : typeof rawHost === 'string' && ID_CHARS.test(rawHost)\n ? rawHost\n : undefined\n if (hostId === undefined) return 'hostId is not a site id'\n // A theme is fields on one site's document, so a theme job that names no\n // site has nothing to propose a change to (AGL-2938).\n if (kind === 'theme' && !hostId) return 'Open a site before changing its theme'\n const rawInputs = body.inputs\n const inputs: Record<string, string | number | boolean> = {}\n if (rawInputs !== undefined && rawInputs !== null) {\n if (typeof rawInputs !== 'object' || Array.isArray(rawInputs)) {\n return 'inputs must be an object'\n }\n for (const [key, value] of Object.entries(rawInputs as Record<string, unknown>)) {\n if (\n typeof value !== 'string' &&\n typeof value !== 'number' &&\n typeof value !== 'boolean'\n ) {\n return `inputs.${key} must be a string, number or boolean`\n }\n inputs[key] = value\n }\n if (JSON.stringify(inputs).length > MAX_INPUTS_JSON_CHARS) {\n return 'inputs are too large'\n }\n }\n const rawModel = typeof body.model === 'string' ? body.model.trim() : ''\n if (rawModel.length > MAX_MODEL_CHARS) return 'model is not a model id'\n // Validated against the plan and the allotment allowlists when each step\n // runs, not here: the allowlists are read inside the step's reservation.\n const model = rawModel && rawModel !== 'auto' ? rawModel : null\n return { orgId, hostId, kind: kind as AiJobKind, brief, inputs, model }\n}\n\nexport async function POST(request: Request): Promise<Response> {\n let payload: unknown\n try {\n payload = await request.json()\n } catch {\n payload = null\n }\n const parsed = parseCreateAiJobBody(payload)\n // The org is parsed BEFORE the ladder so a request that named none is\n // refused with the ladder's 400 rather than a shape error; every other\n // shape fault waits until the caller has proven who they are.\n const orgId = typeof parsed === 'string' ? String((payload as Record<string, unknown> | null)?.orgId ?? '') : parsed.orgId\n // The site rides along for the same reason: a collaborator's `ai.generate`\n // is decided per site (AGL-2927), so the ladder needs it before it can say\n // whether this member may generate here at all.\n const hostId = typeof parsed === 'string' ? null : parsed.hostId\n const gate = await aiGateLadder(\n { request, orgId, hostId },\n {\n feature: 'aiGenerative',\n releaseFlag: 'release_ai_generative',\n lockdownFeature: 'ai-generate',\n permission: 'ai.generate',\n rateLimit: AI_JOBS_RATE_LIMIT,\n },\n )\n if (gate instanceof Response) return gate\n if (typeof parsed === 'string') {\n // Admitted, reserved, and then found to have sent nothing runnable: the\n // reservation goes back, because no token was spent.\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n return Response.json({ error: parsed }, { status: 400 })\n }\n // What only this kind can say about the request (AGL-2909): a site it\n // needs, inputs it reads, an allowance its draft counts against. Asked\n // before the job exists, so a refusal spends nothing.\n let refusal: Awaited<ReturnType<typeof aiJobAdmissionRefusal>>\n try {\n // A site that switched AI off first (AGL-3028), then the kind's own check.\n refusal =\n (await aiJobSiteRefusal({\n firestore: gate.firestore,\n org: gate.org,\n hostId: parsed.hostId,\n })) ??\n (await aiJobAdmissionRefusal(parsed.kind, {\n firestore: gate.firestore,\n orgId: gate.orgId,\n hostId: parsed.hostId,\n inputs: parsed.inputs,\n org: gate.org,\n uid: gate.uid,\n }))\n } catch (error) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n console.error('ai job admission failed', { orgId: gate.orgId, kind: parsed.kind, error })\n return Response.json({ error: 'The AI job could not be created' }, { status: 500 })\n }\n if (refusal) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n return Response.json({ error: refusal.error }, { status: refusal.status })\n }\n\n const now = new Date()\n let created: Awaited<ReturnType<typeof createAiJob>>\n try {\n // The guided start's own plan confirmation (AGL-3594), kept only for a\n // site job on a site that has published nothing of the owner's yet.\n const inputs = await aiJobAdmittedInputs(gate.firestore, {\n kind: parsed.kind,\n hostId: parsed.hostId,\n inputs: parsed.inputs,\n })\n created = await createAiJob(\n gate.firestore,\n {\n orgId: gate.orgId,\n hostId: parsed.hostId,\n kind: parsed.kind,\n brief: parsed.brief,\n inputs,\n model: parsed.model,\n createdBy: gate.uid,\n createdByEmail: gate.decoded.email ?? null,\n },\n now,\n )\n } catch (error) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n console.error('ai job create failed', { orgId: gate.orgId, error })\n return Response.json({ error: 'The AI job could not be created' }, { status: 500 })\n }\n const jobId = created.$id\n\n // A first step that needs more time than this request has (AGL-2907) is left\n // queued for the beat, which starts it with a budget of its own: a provider\n // call this request's timeout cut off would be billed upstream and metered\n // nowhere. Nothing ran, so the reservation goes back.\n if (aiJobNextStepMinimumMs(created) > AI_JOB_INLINE_BUDGET_MS) {\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n return Response.json(\n {\n job: aiJobSummary(created, now),\n meter: aiUsageMeter(gate.reservation, { lastCredits: null }),\n },\n { status: 200 },\n )\n }\n\n // The first step, inline, on the reservation the ladder already holds.\n // The runner hands that reservation back itself if the provider is never\n // reached, and re-queues the step when the budget ends first.\n const run = await runAiJobStep(gate.firestore, gate.orgId, jobId, {\n owner: `route:${randomUUID()}`,\n now,\n reservation: gate.reservation,\n org: gate.org,\n signal: AbortSignal.timeout(AI_JOB_INLINE_BUDGET_MS),\n // The caller is on the request, so what the inline step produces is\n // attributed with their address; the beat's steps carry the uid alone.\n actor: { uid: gate.uid, email: gate.decoded.email ?? null },\n // The workspace's AI pause lets staff through, as the ladder above did.\n staff: gate.staff,\n })\n if (run.outcome === 'not-claimable') {\n // A job created a moment ago has a claimable first step; anything else\n // is a fault in the machine, and the reservation must not be stranded.\n await releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(\n () => undefined,\n )\n console.error('ai job first step not claimable', { orgId: gate.orgId, jobId })\n return Response.json({ error: 'The AI job could not be started' }, { status: 500 })\n }\n return Response.json(\n {\n job: aiJobSummary(run.job),\n // The usage strip's envelope (AGL-2942): the reservation the ladder\n // took, with what the inline step spent.\n meter: aiUsageMeter(gate.reservation, {\n lastCredits: run.job.creditsSpent > 0 ? run.job.creditsSpent : null,\n }),\n },\n { status: 200 },\n )\n}\n\nexport async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url)\n const gate = await aiJobsGate(request, url.searchParams.get('orgId') ?? '')\n if (gate instanceof Response) return gate\n const rawStatus = url.searchParams.get('status') ?? ''\n const status = (AI_JOB_STATUSES as readonly string[]).includes(rawStatus)\n ? (rawStatus as AiJobStatus)\n : undefined\n // `active` (AGL-3593): every job not yet settled, which is what the\n // console's top-bar indicator and the Assist launcher count.\n const active = rawStatus === 'active'\n const limit = Number(url.searchParams.get('limit') ?? '')\n const now = new Date()\n const jobs = await listAiJobs(gate.firestore, gate.orgId, {\n ...(status ? { status } : {}),\n ...(active ? { statuses: AI_JOB_ACTIVE_STATUSES } : {}),\n ...(Number.isFinite(limit) && limit > 0 ? { limit } : {}),\n })\n return Response.json(\n {\n jobs: jobs.map((job) => aiJobSummary(job, now)),\n // Whether the workspace spends the Free taste (AGL-3594), so the guided\n // start offers the Free page band from the request it already makes.\n freeTaste: resolveEffectivePlan(gate.org as never) === 'free',\n },\n { status: 200, headers: { 'Cache-Control': 'no-store' } },\n )\n}\n\nexport const dynamic = 'force-dynamic'\nexport const maxDuration = 60\n"],"names":["randomUUID","resolveEffectivePlan","AI_JOB_KINDS","aiGateLadder","aiJobAdmissionRefusal","aiJobSiteRefusal","AI_JOB_BRIEF_MAX_CHARS","aiJobAdmittedInputs","AI_JOB_INLINE_BUDGET_MS","aiJobNextStepMinimumMs","aiJobSummary","createAiJob","listAiJobs","runAiJobStep","releaseAssistMessage","aiUsageMeter","aiJobsGate","AI_JOB_ACTIVE_STATUSES","AI_JOBS_RATE_LIMIT","key","limit","windowMs","MAX_INPUTS_JSON_CHARS","AI_JOB_STATUSES","ID_CHARS","MAX_MODEL_CHARS","parseCreateAiJobBody","payload","body","orgId","String","trim","kind","includes","join","brief","length","toLocaleString","rawHost","hostId","undefined","test","rawInputs","inputs","Array","isArray","value","Object","entries","JSON","stringify","rawModel","model","POST","request","gate","json","parsed","feature","releaseFlag","lockdownFeature","permission","rateLimit","Response","firestore","reservation","catch","error","status","refusal","org","uid","console","now","Date","created","createdBy","createdByEmail","decoded","email","jobId","$id","job","meter","lastCredits","run","owner","signal","AbortSignal","timeout","actor","staff","outcome","creditsSpent","GET","url","URL","searchParams","get","rawStatus","active","Number","jobs","statuses","isFinite","map","freeTaste","headers","dynamic","maxDuration"],"mappings":"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"}
@@ -974,8 +974,9 @@ async function handler(request) {
974
974
  servedBy: 'cache'
975
975
  });
976
976
  }
977
- // A Free workspace's caller must have held an account for a day before
978
- // a model answers it (AGL-2925). Below the docs and cache paths, which
977
+ // A Free workspace's caller must have held an account for
978
+ // `AI_FREE_MIN_ACCOUNT_AGE_HOURS` before a model answers it (AGL-2925);
979
+ // off unless a deployment sets it (AGL-3591). Below the docs and cache paths, which
979
980
  // spend nothing and stay open to everyone; above the reservation, so a
980
981
  // refused day-zero account never moves a counter. Paid workspaces and
981
982
  // staff are not consulted.