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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  43. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  44. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  45. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  46. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  47. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  48. package/src/lib/jobs/ai-job-build-step.js +3 -1
  49. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  51. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  52. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  53. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  54. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  56. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  57. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  58. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  59. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  61. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  62. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  64. package/src/lib/jobs/ai-job-page-language.js +72 -9
  65. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  66. package/src/lib/jobs/ai-job-page-step.js +12 -2
  67. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
  69. package/src/lib/jobs/ai-job-plan-step.js +54 -20
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  72. package/src/lib/jobs/ai-job-site-content.js +443 -0
  73. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  74. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  75. package/src/lib/jobs/ai-job-site-look.js +322 -0
  76. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  77. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  78. package/src/lib/jobs/ai-job-site-step.js +163 -24
  79. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  80. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  81. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  82. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  84. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  85. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  86. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  87. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  89. package/src/lib/layout-language/ai-layout-language.js +92 -9
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  92. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  93. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  94. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  95. package/src/lib/layout-language/ai-layout-store.js +7 -1
  96. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  97. package/src/lib/model/ai-build-progress.js +4 -1
  98. package/src/lib/model/ai-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-edit-job.d.ts +109 -0
  100. package/src/lib/model/ai-edit-job.js +157 -0
  101. package/src/lib/model/ai-edit-job.js.map +1 -0
  102. package/src/lib/model/ai-job-activity.js +1 -0
  103. package/src/lib/model/ai-job-activity.js.map +1 -1
  104. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  105. package/src/lib/model/ai-jobs.types.js.map +1 -1
  106. package/src/lib/model/ai-logic-job.d.ts +18 -1
  107. package/src/lib/model/ai-logic-job.js +24 -3
  108. package/src/lib/model/ai-logic-job.js.map +1 -1
  109. package/src/lib/model/ai-media-credits.d.ts +8 -4
  110. package/src/lib/model/ai-media-credits.js +11 -4
  111. package/src/lib/model/ai-media-credits.js.map +1 -1
  112. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  113. package/src/lib/model/ai-media-kinds.js +98 -0
  114. package/src/lib/model/ai-media-kinds.js.map +1 -0
  115. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  116. package/src/lib/model/ai-site-build-progress.js +28 -11
  117. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  118. package/src/lib/model/ai-site-context.d.ts +111 -0
  119. package/src/lib/model/ai-site-context.js +222 -0
  120. package/src/lib/model/ai-site-context.js.map +1 -0
  121. package/src/lib/model/ai-site-job.d.ts +10 -2
  122. package/src/lib/model/ai-site-job.js +12 -5
  123. package/src/lib/model/ai-site-job.js.map +1 -1
  124. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  125. package/src/lib/model/ai-site-kinds.js +1942 -0
  126. package/src/lib/model/ai-site-kinds.js.map +1 -0
  127. package/src/lib/model/ai-site-look.d.ts +150 -0
  128. package/src/lib/model/ai-site-look.js +1236 -0
  129. package/src/lib/model/ai-site-look.js.map +1 -0
  130. package/src/lib/model/ai-site-memory.d.ts +80 -0
  131. package/src/lib/model/ai-site-memory.js +171 -0
  132. package/src/lib/model/ai-site-memory.js.map +1 -0
  133. package/src/lib/model/ai-site-start.d.ts +10 -1
  134. package/src/lib/model/ai-site-start.js +16 -2
  135. package/src/lib/model/ai-site-start.js.map +1 -1
  136. package/src/lib/model/assist-follow-up.d.ts +92 -0
  137. package/src/lib/model/assist-follow-up.js +128 -0
  138. package/src/lib/model/assist-follow-up.js.map +1 -0
  139. package/src/lib/plugin-config.js +2 -0
  140. package/src/lib/plugin-config.js.map +1 -1
  141. package/src/lib/plugin.js +20 -1
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.d.ts +9 -1
  144. package/src/lib/providers/anthropic.js +143 -9
  145. package/src/lib/providers/anthropic.js.map +1 -1
  146. package/src/lib/providers/catalog.d.ts +37 -7
  147. package/src/lib/providers/catalog.js +103 -11
  148. package/src/lib/providers/catalog.js.map +1 -1
  149. package/src/lib/providers/contract.d.ts +29 -0
  150. package/src/lib/providers/contract.js.map +1 -1
  151. package/src/lib/providers/image-contract.d.ts +22 -1
  152. package/src/lib/providers/image-contract.js +40 -1
  153. package/src/lib/providers/image-contract.js.map +1 -1
  154. package/src/lib/providers/image-endpoints.d.ts +24 -0
  155. package/src/lib/providers/image-endpoints.js +28 -0
  156. package/src/lib/providers/image-endpoints.js.map +1 -0
  157. package/src/lib/providers/model-choice.js +23 -2
  158. package/src/lib/providers/model-choice.js.map +1 -1
  159. package/src/lib/providers/routing.js +18 -0
  160. package/src/lib/providers/routing.js.map +1 -1
  161. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  162. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  163. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  164. package/src/lib/providers/vertex-image.d.ts +13 -27
  165. package/src/lib/providers/vertex-image.js +15 -42
  166. package/src/lib/providers/vertex-image.js.map +1 -1
  167. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  168. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  169. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  170. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  171. package/src/lib/runtime/ai-dev-env.js +72 -0
  172. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  173. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  174. package/src/lib/runtime/ai-dev-replay.js +313 -0
  175. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  176. package/src/lib/runtime/ai-doctrine.js +5 -1
  177. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  178. package/src/lib/runtime/ai-eval-live.js +4 -0
  179. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  180. package/src/lib/runtime/ai-eval.d.ts +13 -1
  181. package/src/lib/runtime/ai-eval.js +84 -1
  182. package/src/lib/runtime/ai-eval.js.map +1 -1
  183. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  184. package/src/lib/runtime/ai-live-batch.js +89 -0
  185. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  186. package/src/lib/runtime/ai-runtime.js +46 -2
  187. package/src/lib/runtime/ai-runtime.js.map +1 -1
  188. package/src/lib/runtime/site-context.d.ts +58 -0
  189. package/src/lib/runtime/site-context.js +127 -0
  190. package/src/lib/runtime/site-context.js.map +1 -0
  191. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  192. package/src/lib/server/ai-admin-conversations.js +197 -0
  193. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  194. package/src/lib/server/ai-media-image.d.ts +5 -5
  195. package/src/lib/server/ai-media-image.js +25 -11
  196. package/src/lib/server/ai-media-image.js.map +1 -1
  197. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  198. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  199. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  200. package/src/lib/server/assist-chat.d.ts +11 -0
  201. package/src/lib/server/assist-chat.js +57 -18
  202. package/src/lib/server/assist-chat.js.map +1 -1
  203. package/src/lib/server/assist-edit-applied.js +16 -3
  204. package/src/lib/server/assist-edit-applied.js.map +1 -1
  205. package/src/lib/server/assist-view-context.d.ts +19 -1
  206. package/src/lib/server/assist-view-context.js +38 -1
  207. package/src/lib/server/assist-view-context.js.map +1 -1
  208. package/src/lib/server.js +10 -0
  209. package/src/lib/server.js.map +1 -1
  210. package/src/lib/subprocessors.js +17 -1
  211. package/src/lib/subprocessors.js.map +1 -1
  212. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  213. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  214. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  215. package/src/lib/usage/assist-usage.d.ts +3 -0
  216. package/src/lib/usage/assist-usage.js +4 -2
  217. package/src/lib/usage/assist-usage.js.map +1 -1
  218. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  219. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  220. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiProvider, AiProviderRequest, AiResult } from '../providers/contract';
18
+ import type { AiDevEnv as Env } from './ai-dev-env';
19
+ /**
20
+ * A LIVE EVAL ROUND AS ONE MESSAGE BATCH (AGL-3660), at half the price.
21
+ *
22
+ * `AGLYN_LIVE_AI_BATCH=1` turns every non-streaming request a live eval sends
23
+ * into an entry of a batch instead of a request of its own. The eval code does
24
+ * not change: each request still awaits its own answer. Requests that arrive
25
+ * together — a `Promise.all` over the briefs — gather into one batch, which
26
+ * is sent once nothing new has arrived for `AGLYN_LIVE_AI_BATCH_WINDOW_MS`
27
+ * (2 s), and every request resolves when the batch has ended. A re-ask is
28
+ * asked only once its answer has been read, so the re-asks of a round gather
29
+ * into the NEXT batch on their own: a round with re-asks is two or three
30
+ * batches, never a request apiece.
31
+ *
32
+ * A batch takes minutes rather than seconds (most end within the hour), so it
33
+ * suits the full sweep an agent runs before landing, not the one brief it is
34
+ * iterating on. Streams are never batched. Development only: refused in any
35
+ * deployed server, where nothing should wait minutes for an answer.
36
+ */
37
+ /** How long the queue waits for one more request before it sends the batch. */
38
+ export declare const AI_LIVE_BATCH_WINDOW_ENV = "AGLYN_LIVE_AI_BATCH_WINDOW_MS";
39
+ export declare function aiLiveBatchStats(): {
40
+ batches: number;
41
+ requests: number;
42
+ };
43
+ export declare function resetAiLiveBatchStats(): void;
44
+ /** One request as an entry of the next batch to `provider`. */
45
+ export declare function aiCompleteInBatch(provider: AiProvider, request: AiProviderRequest, env?: Env): Promise<AiResult>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ /**
18
+ * A LIVE EVAL ROUND AS ONE MESSAGE BATCH (AGL-3660), at half the price.
19
+ *
20
+ * `AGLYN_LIVE_AI_BATCH=1` turns every non-streaming request a live eval sends
21
+ * into an entry of a batch instead of a request of its own. The eval code does
22
+ * not change: each request still awaits its own answer. Requests that arrive
23
+ * together — a `Promise.all` over the briefs — gather into one batch, which
24
+ * is sent once nothing new has arrived for `AGLYN_LIVE_AI_BATCH_WINDOW_MS`
25
+ * (2 s), and every request resolves when the batch has ended. A re-ask is
26
+ * asked only once its answer has been read, so the re-asks of a round gather
27
+ * into the NEXT batch on their own: a round with re-asks is two or three
28
+ * batches, never a request apiece.
29
+ *
30
+ * A batch takes minutes rather than seconds (most end within the hour), so it
31
+ * suits the full sweep an agent runs before landing, not the one brief it is
32
+ * iterating on. Streams are never batched. Development only: refused in any
33
+ * deployed server, where nothing should wait minutes for an answer.
34
+ */ /** How long the queue waits for one more request before it sends the batch. */ export const AI_LIVE_BATCH_WINDOW_ENV = 'AGLYN_LIVE_AI_BATCH_WINDOW_MS';
35
+ const queues = new Map();
36
+ /** How many batches this process has sent, and how many requests rode in them. */ const counts = {
37
+ batches: 0,
38
+ requests: 0
39
+ };
40
+ export function aiLiveBatchStats() {
41
+ return _extends({}, counts);
42
+ }
43
+ export function resetAiLiveBatchStats() {
44
+ counts.batches = 0;
45
+ counts.requests = 0;
46
+ }
47
+ function windowMs(env) {
48
+ const parsed = Number(env[AI_LIVE_BATCH_WINDOW_ENV]);
49
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 2000;
50
+ }
51
+ async function flush(provider) {
52
+ const queue = queues.get(provider);
53
+ if (!queue) return;
54
+ const entries = queue.entries;
55
+ queues.delete(provider);
56
+ if (!entries.length || !provider.completeBatch) return;
57
+ counts.batches += 1;
58
+ counts.requests += entries.length;
59
+ try {
60
+ const results = await provider.completeBatch(entries.map((entry)=>entry.request));
61
+ entries.forEach((entry, index)=>{
62
+ const result = results[index];
63
+ if (result instanceof Error || !result) entry.reject(result != null ? result : new Error('the batch returned no result'));
64
+ else entry.resolve(result);
65
+ });
66
+ } catch (error) {
67
+ for (const entry of entries)entry.reject(error);
68
+ }
69
+ }
70
+ /** One request as an entry of the next batch to `provider`. */ export function aiCompleteInBatch(provider, request, env = process.env) {
71
+ if (!provider.completeBatch) return provider.complete(request);
72
+ return new Promise((resolve, reject)=>{
73
+ var _queues_get;
74
+ const queue = (_queues_get = queues.get(provider)) != null ? _queues_get : {
75
+ entries: [],
76
+ timer: null
77
+ };
78
+ queues.set(provider, queue);
79
+ queue.entries.push({
80
+ request,
81
+ resolve,
82
+ reject
83
+ });
84
+ if (queue.timer) clearTimeout(queue.timer);
85
+ queue.timer = setTimeout(()=>void flush(provider), windowMs(env));
86
+ });
87
+ }
88
+
89
+ //# sourceMappingURL=ai-live-batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-live-batch.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiProvider, AiProviderRequest, AiResult } from '../providers/contract'\nimport type { AiDevEnv as Env } from './ai-dev-env'\n\n/**\n * A LIVE EVAL ROUND AS ONE MESSAGE BATCH (AGL-3660), at half the price.\n *\n * `AGLYN_LIVE_AI_BATCH=1` turns every non-streaming request a live eval sends\n * into an entry of a batch instead of a request of its own. The eval code does\n * not change: each request still awaits its own answer. Requests that arrive\n * together — a `Promise.all` over the briefs — gather into one batch, which\n * is sent once nothing new has arrived for `AGLYN_LIVE_AI_BATCH_WINDOW_MS`\n * (2 s), and every request resolves when the batch has ended. A re-ask is\n * asked only once its answer has been read, so the re-asks of a round gather\n * into the NEXT batch on their own: a round with re-asks is two or three\n * batches, never a request apiece.\n *\n * A batch takes minutes rather than seconds (most end within the hour), so it\n * suits the full sweep an agent runs before landing, not the one brief it is\n * iterating on. Streams are never batched. Development only: refused in any\n * deployed server, where nothing should wait minutes for an answer.\n */\n\n/** How long the queue waits for one more request before it sends the batch. */\nexport const AI_LIVE_BATCH_WINDOW_ENV = 'AGLYN_LIVE_AI_BATCH_WINDOW_MS'\n\n\ninterface Queued {\n request: AiProviderRequest\n resolve: (result: AiResult) => void\n reject: (error: unknown) => void\n}\n\nconst queues = new Map<AiProvider, { entries: Queued[]; timer: ReturnType<typeof setTimeout> | null }>()\n\n/** How many batches this process has sent, and how many requests rode in them. */\nconst counts = { batches: 0, requests: 0 }\n\nexport function aiLiveBatchStats(): { batches: number; requests: number } {\n return { ...counts }\n}\n\nexport function resetAiLiveBatchStats(): void {\n counts.batches = 0\n counts.requests = 0\n}\n\nfunction windowMs(env: Env): number {\n const parsed = Number(env[AI_LIVE_BATCH_WINDOW_ENV])\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 2_000\n}\n\nasync function flush(provider: AiProvider): Promise<void> {\n const queue = queues.get(provider)\n if (!queue) return\n const entries = queue.entries\n queues.delete(provider)\n if (!entries.length || !provider.completeBatch) return\n counts.batches += 1\n counts.requests += entries.length\n try {\n const results = await provider.completeBatch(entries.map((entry) => entry.request))\n entries.forEach((entry, index) => {\n const result = results[index]\n if (result instanceof Error || !result) entry.reject(result ?? new Error('the batch returned no result'))\n else entry.resolve(result)\n })\n } catch (error) {\n for (const entry of entries) entry.reject(error)\n }\n}\n\n/** One request as an entry of the next batch to `provider`. */\nexport function aiCompleteInBatch(\n provider: AiProvider,\n request: AiProviderRequest,\n env: Env = process.env,\n): Promise<AiResult> {\n if (!provider.completeBatch) return provider.complete(request)\n return new Promise<AiResult>((resolve, reject) => {\n const queue = queues.get(provider) ?? { entries: [], timer: null }\n queues.set(provider, queue)\n queue.entries.push({ request, resolve, reject })\n if (queue.timer) clearTimeout(queue.timer)\n queue.timer = setTimeout(() => void flush(provider), windowMs(env))\n })\n}\n"],"names":["AI_LIVE_BATCH_WINDOW_ENV","queues","Map","counts","batches","requests","aiLiveBatchStats","resetAiLiveBatchStats","windowMs","env","parsed","Number","isFinite","flush","provider","queue","get","entries","delete","length","completeBatch","results","map","entry","request","forEach","index","result","Error","reject","resolve","error","aiCompleteInBatch","process","complete","Promise","timer","set","push","clearTimeout","setTimeout"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAKD;;;;;;;;;;;;;;;;;CAiBC,GAED,6EAA6E,GAC7E,OAAO,MAAMA,2BAA2B,gCAA+B;AASvE,MAAMC,SAAS,IAAIC;AAEnB,gFAAgF,GAChF,MAAMC,SAAS;IAAEC,SAAS;IAAGC,UAAU;AAAE;AAEzC,OAAO,SAASC;IACd,OAAO,aAAKH;AACd;AAEA,OAAO,SAASI;IACdJ,OAAOC,OAAO,GAAG;IACjBD,OAAOE,QAAQ,GAAG;AACpB;AAEA,SAASG,SAASC,GAAQ;IACxB,MAAMC,SAASC,OAAOF,GAAG,CAACT,yBAAyB;IACnD,OAAOW,OAAOC,QAAQ,CAACF,WAAWA,UAAU,IAAIA,SAAS;AAC3D;AAEA,eAAeG,MAAMC,QAAoB;IACvC,MAAMC,QAAQd,OAAOe,GAAG,CAACF;IACzB,IAAI,CAACC,OAAO;IACZ,MAAME,UAAUF,MAAME,OAAO;IAC7BhB,OAAOiB,MAAM,CAACJ;IACd,IAAI,CAACG,QAAQE,MAAM,IAAI,CAACL,SAASM,aAAa,EAAE;IAChDjB,OAAOC,OAAO,IAAI;IAClBD,OAAOE,QAAQ,IAAIY,QAAQE,MAAM;IACjC,IAAI;QACF,MAAME,UAAU,MAAMP,SAASM,aAAa,CAACH,QAAQK,GAAG,CAAC,CAACC,QAAUA,MAAMC,OAAO;QACjFP,QAAQQ,OAAO,CAAC,CAACF,OAAOG;YACtB,MAAMC,SAASN,OAAO,CAACK,MAAM;YAC7B,IAAIC,kBAAkBC,SAAS,CAACD,QAAQJ,MAAMM,MAAM,CAACF,iBAAAA,SAAU,IAAIC,MAAM;iBACpEL,MAAMO,OAAO,CAACH;QACrB;IACF,EAAE,OAAOI,OAAO;QACd,KAAK,MAAMR,SAASN,QAASM,MAAMM,MAAM,CAACE;IAC5C;AACF;AAEA,6DAA6D,GAC7D,OAAO,SAASC,kBACdlB,QAAoB,EACpBU,OAA0B,EAC1Bf,MAAWwB,QAAQxB,GAAG;IAEtB,IAAI,CAACK,SAASM,aAAa,EAAE,OAAON,SAASoB,QAAQ,CAACV;IACtD,OAAO,IAAIW,QAAkB,CAACL,SAASD;YACvB5B;QAAd,MAAMc,SAAQd,cAAAA,OAAOe,GAAG,CAACF,qBAAXb,cAAwB;YAAEgB,SAAS,EAAE;YAAEmB,OAAO;QAAK;QACjEnC,OAAOoC,GAAG,CAACvB,UAAUC;QACrBA,MAAME,OAAO,CAACqB,IAAI,CAAC;YAAEd;YAASM;YAASD;QAAO;QAC9C,IAAId,MAAMqB,KAAK,EAAEG,aAAaxB,MAAMqB,KAAK;QACzCrB,MAAMqB,KAAK,GAAGI,WAAW,IAAM,KAAK3B,MAAMC,WAAWN,SAASC;IAChE;AACF"}
@@ -18,6 +18,8 @@ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_wi
18
18
  */ import { aiModelCacheMinTokens } from "../providers/catalog.js";
19
19
  import { AI_IMAGE_MAX_BYTES, AI_IMAGE_MEDIA_TYPES, AI_REQUEST_MAX_IMAGES, AiRequestShapeError, AiUpstreamError, aiBase64Bytes, aiMessageImages } from "../providers/contract.js";
20
20
  import { resolveAiProvider } from "../providers/routing.js";
21
+ import { aiDevCacheTtl, aiDevReplayMode, aiIsDeployedRuntime, aiLiveBatchWanted, aiWarnIfReplayAskedInDeployment } from "./ai-dev-env.js";
22
+ import { aiCompleteInBatch } from "./ai-live-batch.js";
21
23
  export { AI_UPSTREAM_FAILURE_COPY, AiRequestShapeError, AiUpstreamError } from "../providers/contract.js";
22
24
  /**
23
25
  * The one runtime every AI door calls (AGL-2903), provider-generic since
@@ -84,6 +86,13 @@ When a brief is ordinary business content, write it well and do not lecture.`;
84
86
  throw new AiRequestShapeError(`system block ${index} is volatile but sits inside the cached prefix (last breakpoint at block ${lastBreakpoint})`);
85
87
  }
86
88
  });
89
+ // A site's block is cached per site (AGL-3661), so it closes the cached
90
+ // span: a platform-wide breakpoint after it would be keyed on the site too,
91
+ // and every workspace would warm its own copy of a shared prompt.
92
+ const firstSite = system.findIndex((block)=>block.site && block.cacheBreakpoint);
93
+ if (firstSite >= 0 && firstSite !== lastBreakpoint) {
94
+ throw new AiRequestShapeError(`system block ${firstSite} is a site's cached block but breakpoint ${lastBreakpoint} follows it`);
95
+ }
87
96
  }
88
97
  /**
89
98
  * Whether a provider's model reads a picture in a user turn (AGL-2916): what
@@ -220,6 +229,7 @@ function providerFor(input) {
220
229
  };
221
230
  }
222
231
  export async function runAiRequest(input) {
232
+ var _request_cacheTtl;
223
233
  validateAiSystemBlocks(input.system);
224
234
  const { provider, apiKey } = providerFor(input);
225
235
  validateAiMessages(input.messages, ()=>aiModelReadsImages(provider, input.model));
@@ -228,16 +238,50 @@ export async function runAiRequest(input) {
228
238
  "settings",
229
239
  "provider"
230
240
  ]);
231
- const providerRequest = _extends({}, request, {
241
+ aiWarnIfReplayAskedInDeployment();
242
+ const cacheTtl = (_request_cacheTtl = request.cacheTtl) != null ? _request_cacheTtl : aiDevCacheTtl();
243
+ const providerRequest = _extends({}, request, cacheTtl ? {
244
+ cacheTtl
245
+ } : {}, {
232
246
  apiKey
233
247
  });
234
248
  try {
235
- return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest));
249
+ if (aiIsDeployedRuntime()) {
250
+ return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest));
251
+ }
252
+ return await runInDevelopment(provider, providerRequest, stream);
236
253
  } catch (error) {
237
254
  await reportAccountProblem(provider, error);
238
255
  throw error;
239
256
  }
240
257
  }
258
+ /**
259
+ * A request in a development process (AGL-3660): through the replay cache,
260
+ * batch mode and the first-request-warms-the-prefix rule when this process
261
+ * asked for them (`ai-dev-env.ts`), straight to the provider when it asked
262
+ * for none. The file-backed half is loaded only when a layer is on, so a dev
263
+ * server that never asked loads nothing it did not before.
264
+ */ async function runInDevelopment(provider, request, stream) {
265
+ const mode = aiDevReplayMode();
266
+ const batch = !stream && aiLiveBatchWanted() && Boolean(provider.completeBatch);
267
+ if (mode === 'off' && !batch) {
268
+ return stream ? provider.stream(request) : provider.complete(request);
269
+ }
270
+ const dev = await import("./ai-dev-replay.js");
271
+ const ttlMs = request.cacheTtl === '1h' ? 60 * 60000 : 5 * 60000;
272
+ const prefix = dev.aiCachedPrefixKey(request);
273
+ if (stream) {
274
+ const live = ()=>dev.aiDevWarmPrefixFirst(prefix, ttlMs, ()=>provider.stream(request));
275
+ return mode === 'off' ? live() : dev.aiDevReplayStream(provider, request, mode, live);
276
+ }
277
+ const live = async ()=>{
278
+ const result = batch ? await aiCompleteInBatch(provider, request) : await dev.aiDevWarmPrefixFirst(prefix, ttlMs, ()=>provider.complete(request));
279
+ dev.aiDevCountLive(result.usage, request.model, batch ? AI_BATCH_PRICE_FACTOR : 1);
280
+ return result;
281
+ };
282
+ return mode === 'off' ? live() : dev.aiDevReplayComplete(provider, request, mode, live);
283
+ }
284
+ /** What a batch's discount leaves of a request's price, for the development count. */ const AI_BATCH_PRICE_FACTOR = 0.5;
241
285
  /**
242
286
  * Tells the operator when the failure is the platform's provider account —
243
287
  * a refused key, no credit — rather than the request (AGL-3377). Once per
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-runtime.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 { aiModelCacheMinTokens } from '../providers/catalog'\nimport {\n AI_IMAGE_MAX_BYTES,\n AI_IMAGE_MEDIA_TYPES,\n AI_REQUEST_MAX_IMAGES,\n AiRequestShapeError,\n AiUpstreamError,\n aiBase64Bytes,\n aiMessageImages,\n type AiMessage,\n type AiProvider,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiTool,\n} from '../providers/contract'\nimport { resolveAiProvider, type AiPluginSettings } from '../providers/routing'\n\nexport {\n AI_UPSTREAM_FAILURE_COPY,\n AiRequestShapeError,\n AiUpstreamError,\n type AiCompletion,\n type AiEffort,\n type AiImagePart,\n type AiMessage,\n type AiMessagePart,\n type AiRefusal,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiThinking,\n type AiTool,\n type AiToolUse,\n type AiUsage,\n} from '../providers/contract'\n\n/**\n * The one runtime every AI door calls (AGL-2903), provider-generic since\n * AGL-2939.\n *\n * `/api/assist/chat` and `/api/ai/assist` each carried their own `fetch` to\n * one vendor, their own SSE parser and their own usage extraction, and a\n * third door would have been a third copy. This module owns the request\n * shape, the cache-prefix rule, the provider resolution and the error\n * boundary; an adapter owns one vendor's wire protocol; a door owns its\n * prompt, its gate ladder and its wire format.\n *\n * The cache-prefix rule (AGL-2352) is enforced HERE, before any adapter:\n * a block marked `volatile` may not sit at or before the last\n * `cacheBreakpoint`, and more breakpoints than any provider caches are\n * refused. A provider without a cache ignores the markers and keeps the\n * order, so a request that is right for the caching adapter is right for\n * every adapter.\n *\n * The picture rule (AGL-2916) is enforced here too, and for the same reason:\n * a picture on an assistant turn, a picture for a model whose provider does\n * not say it reads one, or a picture past the bounds is refused before any\n * adapter is reached, so no door can send one a provider would reject.\n */\n\n/** The most `cacheBreakpoint` markers a request may carry. */\nexport const AI_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * The acceptable-use rules every generation prompt carries (AGL-2925).\n *\n * ONE constant, appended to each door's STATIC system text — inside the\n * cached prefix on the console chat, ahead of the mode prompt on the copy\n * assistant — so the rules the model is held to are the same at every\n * door and carry no per-org byte. The Free taste is what makes them worth\n * stating: a generated page that nobody paid for is the cheapest phishing\n * kit there is, and the model declining it is the first line, ahead of the\n * abuse-report route and the lockdown key.\n *\n * The categories are the platform's own Acceptable Use Policy, restated in\n * the second person for the model. A decline from the model's safety layer\n * arrives as a refusal, which the meter counts against the account's daily\n * pause and never against its credits.\n */\nexport const AI_ACCEPTABLE_USE_BLOCK = `Acceptable use — decline these rather than write them, and say briefly why:\n- Pages, emails or copy that harvest credentials, payment details or personal data by deception (phishing, fake sign-in or checkout pages, fake \"verify your account\" notices).\n- Impersonating a real business, brand, public body or person you were not told the user represents — their name, logo, sign-in page or voice — or copy that claims an endorsement, certification or partnership that was not given to you as a fact.\n- Content that promotes or sells prohibited goods and services: weapons, illegal drugs, counterfeit goods, malware or deceptive downloads, unlicensed gambling, or adult content involving minors.\n- Scams and fraud: fake investment or crypto schemes, advance-fee offers, tech-support scams, fake charities, deceptive \"you have won\" pages, or copy built to defraud.\n- Harassment, threats, or hateful content targeting a person or a group.\n- Spam farms: bulk near-duplicate pages or copy whose only purpose is to game search engines or send unsolicited mail.\nWhen a brief is ordinary business content, write it well and do not lecture.`\n\n/** A door's request, with the org's settings when the door knows the org. */\ninterface AiRuntimeOptions {\n /** The org's resolved `pluginSettings/ai`, for its provider and model choices. */\n settings?: AiPluginSettings\n /** A provider chosen by the caller, ahead of every setting; specs use it. */\n provider?: AiProvider\n}\n\nexport interface AiRequest extends AiRequestBase, AiRuntimeOptions {\n stream: false\n}\n\nexport interface AiStreamRequest extends AiRequestBase, AiRuntimeOptions {\n stream: true\n}\n\n/**\n * The cache guard, applied to the system blocks before any adapter sees\n * them. Exported so a spec and a door can assert a prompt's shape without\n * a provider.\n */\nexport function validateAiSystemBlocks(system: readonly AiSystemBlock[]): void {\n const breakpoints = system.filter((block) => block.cacheBreakpoint)\n if (breakpoints.length > AI_MAX_CACHE_BREAKPOINTS) {\n throw new AiRequestShapeError(\n `${breakpoints.length} cache breakpoints; at most ${AI_MAX_CACHE_BREAKPOINTS} are cached`,\n )\n }\n // Caching is a prefix match, so the LAST breakpoint decides what is\n // cached, and every block at or before it is inside the cached prefix. A\n // volatile block anywhere in that span — including one that is itself\n // the breakpoint — puts a per-request byte into the prefix (AGL-2352).\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n system.forEach((block, index) => {\n if (block.volatile && index <= lastBreakpoint) {\n throw new AiRequestShapeError(\n `system block ${index} is volatile but sits inside the cached prefix (last breakpoint at block ${lastBreakpoint})`,\n )\n }\n })\n}\n\n/**\n * Whether a provider's model reads a picture in a user turn (AGL-2916): what\n * the provider's own descriptor of the model says. A model the provider does\n * not describe reads none, so an adopter's provider is never sent a picture\n * it did not say it can take.\n */\nexport function aiModelReadsImages(provider: AiProvider, model: string): boolean {\n return provider\n .models()\n .some((descriptor) => descriptor.id === model && descriptor.capabilities.vision === true)\n}\n\n/**\n * Whether a model a door resolved reads pictures on the provider that would\n * serve the request: what a door asks before it attaches one, so a model that\n * does not is sent the text alone rather than refused.\n */\nexport function aiRouteReadsImages(model: string, settings?: AiPluginSettings): boolean {\n const provider = resolveAiProvider(settings)\n return provider ? aiModelReadsImages(provider, model) : false\n}\n\n/** A character standard base64 does not use, outside its padding. */\nconst NOT_BASE64 = /[^A-Za-z0-9+/]/\n\n/**\n * Whether `data` is standard base64 and nothing else: its alphabet, then at\n * most two `=` of padding; no `data:` prefix, no line breaks.\n *\n * Scanned for a character outside the alphabet rather than matched whole: a\n * picture is megabytes of base64, and a whole-string pattern with a greedy\n * loop can exhaust the regular expression engine's backtrack stack on input\n * that large, which throws a RangeError instead of answering.\n */\nfunction isBareBase64(data: string): boolean {\n const body = data.endsWith('==') ? data.slice(0, -2) : data.endsWith('=') ? data.slice(0, -1) : data\n return body.length > 0 && !NOT_BASE64.test(body)\n}\n\n/**\n * The picture guard (AGL-2916), applied to the messages before any adapter\n * sees them. A picture rides only a user turn, only to a model that reads\n * pictures, only in a format every such model takes, as bare base64 within\n * the size a provider accepts, and no more of them than a request may carry.\n * `readsImages` is asked only when a turn carries a picture. Exported so a\n * spec and a door can assert a request's shape without a provider.\n */\nexport function validateAiMessages(messages: readonly AiMessage[], readsImages: () => boolean): void {\n messages.forEach((message, index) => {\n if (typeof message.content === 'string') return\n if (!Array.isArray(message.content) || message.content.length === 0) {\n throw new AiRequestShapeError(`message ${index} has neither text nor parts`)\n }\n for (const part of message.content) {\n if (part.type === 'text') {\n if (typeof part.text !== 'string') {\n throw new AiRequestShapeError(`message ${index} has a text part with no text`)\n }\n continue\n }\n if (part.type !== 'image') {\n throw new AiRequestShapeError(`message ${index} has a part of an unknown type`)\n }\n if (message.role !== 'user') {\n throw new AiRequestShapeError(`message ${index} is an assistant turn and carries a picture`)\n }\n if (!(AI_IMAGE_MEDIA_TYPES as readonly string[]).includes(part.mediaType)) {\n throw new AiRequestShapeError(`message ${index} carries a picture of an unsupported type`)\n }\n if (typeof part.data !== 'string') {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n // The size first: it needs only the length, so an oversized picture is\n // refused without reading its bytes.\n if (aiBase64Bytes(part.data) > AI_IMAGE_MAX_BYTES) {\n throw new AiRequestShapeError(\n `message ${index} carries a picture over ${AI_IMAGE_MAX_BYTES} bytes`,\n )\n }\n if (!isBareBase64(part.data)) {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n }\n })\n const images = aiMessageImages(messages).length\n if (images > AI_REQUEST_MAX_IMAGES) {\n throw new AiRequestShapeError(`${images} pictures; a request carries at most ${AI_REQUEST_MAX_IMAGES}`)\n }\n if (images > 0 && !readsImages()) {\n throw new AiRequestShapeError('the request carries a picture and its model does not read pictures')\n }\n}\n\n/**\n * Characters to a token when a prompt is measured rather than tokenized\n * (AGL-2937). Four is the high end of English prose, so reading a prefix\n * through it errs LOW on the token count — and a prefix counts as cached\n * only when it clears its model's minimum even at that reading. Erring the\n * other way would let a prompt claim a cache it does not get.\n */\nexport const AI_CACHE_PREFIX_TOKEN_CHARS = 4\n\n/**\n * The characters inside the cached span of a request: the tools, which a\n * provider renders ahead of the system blocks and which therefore sit in\n * every prefix, plus every system block up to and including the last\n * breakpoint. Zero when the request marks no breakpoint at all.\n */\nexport function aiCachedPrefixChars(\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): number {\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n if (lastBreakpoint < 0) return 0\n const toolChars = tools.reduce((total, tool) => total + JSON.stringify(tool).length, 0)\n return system\n .slice(0, lastBreakpoint + 1)\n .reduce((total, block) => total + block.text.length, toolChars)\n}\n\n/**\n * Whether this request's cached span is long enough for the model to cache\n * it at all (AGL-2937). A prefix under the model's `cacheMinTokens` is\n * served with the breakpoints honored and nothing cached — no error, and a\n * usage report that reads as a permanent miss — so a door designed around a\n * cache has to answer this question before it believes its own markers, and\n * `runtime/ai-prompt-cache.spec.ts` answers it for every door at once.\n */\nexport function aiCachedPrefixCaches(\n model: string,\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): boolean {\n const chars = aiCachedPrefixChars(system, tools)\n return chars / AI_CACHE_PREFIX_TOKEN_CHARS >= aiModelCacheMinTokens(model)\n}\n\n/**\n * Whether a provider is configured to answer: registered, and its key\n * present. Every door asks this before it spends a reservation, and\n * answers 501 (or the docs fallback) when it is false.\n */\nexport function aiProviderReady(settings?: AiPluginSettings): boolean {\n return Boolean(resolveAiProvider(settings)?.readApiKey())\n}\n\n/** The provider that would answer, for a door that names it in its logs or its inventory. */\nexport function aiActiveProvider(settings?: AiPluginSettings): AiProvider | undefined {\n return resolveAiProvider(settings)\n}\n\nfunction providerFor(input: AiRuntimeOptions): { provider: AiProvider; apiKey: string } {\n const provider = input.provider ?? resolveAiProvider(input.settings)\n if (!provider) throw new Error('no AI provider is registered')\n const apiKey = provider.readApiKey()\n if (!apiKey) {\n // Every door gates on `aiProviderReady` before it gets here, so this is\n // a wiring fault rather than a customer path.\n throw new Error(`${provider.apiKeyEnv} is not set`)\n }\n return { provider, apiKey }\n}\n\n/**\n * Send one request through the resolved provider.\n *\n * Streaming: resolves once the provider has ACCEPTED the request — after\n * the headers, before the first token — with an iterator of simplified\n * events the caller wraps in its own wire format. A non-2xx answer rejects\n * with `AiUpstreamError` before the iterator exists, so a door can still\n * send a plain error status and hand back its reservation.\n *\n * Non-streaming: resolves with the parsed result, a decline typed as\n * `AiRefusal` rather than thrown.\n */\nexport async function runAiRequest(\n input: AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent>>\nexport async function runAiRequest(input: AiRequest): Promise<AiResult>\nexport async function runAiRequest(\n input: AiRequest | AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent> | AiResult> {\n validateAiSystemBlocks(input.system)\n const { provider, apiKey } = providerFor(input)\n validateAiMessages(input.messages, () => aiModelReadsImages(provider, input.model))\n const { stream, settings: _settings, provider: _provider, ...request } = input\n const providerRequest = { ...request, apiKey }\n try {\n return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest))\n } catch (error) {\n await reportAccountProblem(provider, error)\n throw error\n }\n}\n\n/**\n * Tells the operator when the failure is the platform's provider account —\n * a refused key, no credit — rather than the request (AGL-3377). Once per\n * provider and problem per window; loaded lazily so the runtime pulls no\n * Admin SDK until an account is actually broken. Never throws.\n */\nasync function reportAccountProblem(provider: AiProvider, error: unknown): Promise<void> {\n if (!(error instanceof AiUpstreamError) || !error.accountProblem) return\n try {\n const [{ raiseOperatorAlert }, { AI_PROVIDER_UNAVAILABLE }] = await Promise.all([\n import('./operator-alerts-seam'),\n import('../operator-alerts'),\n ])\n await raiseOperatorAlert(AI_PROVIDER_UNAVAILABLE, {\n dedupeKey: `${provider.id}:${error.accountProblem}`,\n context: {\n provider: provider.label,\n keyEnv: provider.apiKeyEnv,\n reason:\n error.accountProblem === 'credentials'\n ? `its API key was refused (HTTP ${error.status ?? 'unknown'})`\n : 'the account is out of credit',\n },\n })\n } catch (alertError) {\n console.error('[ai] the provider account alert could not be raised', alertError)\n }\n}\n"],"names":["aiModelCacheMinTokens","AI_IMAGE_MAX_BYTES","AI_IMAGE_MEDIA_TYPES","AI_REQUEST_MAX_IMAGES","AiRequestShapeError","AiUpstreamError","aiBase64Bytes","aiMessageImages","resolveAiProvider","AI_UPSTREAM_FAILURE_COPY","AI_MAX_CACHE_BREAKPOINTS","AI_ACCEPTABLE_USE_BLOCK","validateAiSystemBlocks","system","breakpoints","filter","block","cacheBreakpoint","length","lastBreakpoint","reduce","last","index","forEach","volatile","aiModelReadsImages","provider","model","models","some","descriptor","id","capabilities","vision","aiRouteReadsImages","settings","NOT_BASE64","isBareBase64","data","body","endsWith","slice","test","validateAiMessages","messages","readsImages","message","content","Array","isArray","part","type","text","role","includes","mediaType","images","AI_CACHE_PREFIX_TOKEN_CHARS","aiCachedPrefixChars","tools","toolChars","total","tool","JSON","stringify","aiCachedPrefixCaches","chars","aiProviderReady","Boolean","readApiKey","aiActiveProvider","providerFor","input","Error","apiKey","apiKeyEnv","runAiRequest","stream","_settings","_provider","request","providerRequest","complete","error","reportAccountProblem","accountProblem","raiseOperatorAlert","AI_PROVIDER_UNAVAILABLE","Promise","all","dedupeKey","context","label","keyEnv","reason","status","alertError","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-runtime.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 { aiModelCacheMinTokens } from '../providers/catalog'\nimport {\n AI_IMAGE_MAX_BYTES,\n AI_IMAGE_MEDIA_TYPES,\n AI_REQUEST_MAX_IMAGES,\n AiRequestShapeError,\n AiUpstreamError,\n aiBase64Bytes,\n aiMessageImages,\n type AiMessage,\n type AiProvider,\n type AiProviderRequest,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiTool,\n} from '../providers/contract'\nimport { resolveAiProvider, type AiPluginSettings } from '../providers/routing'\nimport {\n aiDevCacheTtl,\n aiDevReplayMode,\n aiIsDeployedRuntime,\n aiLiveBatchWanted,\n aiWarnIfReplayAskedInDeployment,\n} from './ai-dev-env'\nimport { aiCompleteInBatch } from './ai-live-batch'\n\nexport {\n AI_UPSTREAM_FAILURE_COPY,\n AiRequestShapeError,\n AiUpstreamError,\n type AiCompletion,\n type AiEffort,\n type AiImagePart,\n type AiMessage,\n type AiMessagePart,\n type AiRefusal,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiThinking,\n type AiTool,\n type AiToolUse,\n type AiUsage,\n} from '../providers/contract'\n\n/**\n * The one runtime every AI door calls (AGL-2903), provider-generic since\n * AGL-2939.\n *\n * `/api/assist/chat` and `/api/ai/assist` each carried their own `fetch` to\n * one vendor, their own SSE parser and their own usage extraction, and a\n * third door would have been a third copy. This module owns the request\n * shape, the cache-prefix rule, the provider resolution and the error\n * boundary; an adapter owns one vendor's wire protocol; a door owns its\n * prompt, its gate ladder and its wire format.\n *\n * The cache-prefix rule (AGL-2352) is enforced HERE, before any adapter:\n * a block marked `volatile` may not sit at or before the last\n * `cacheBreakpoint`, and more breakpoints than any provider caches are\n * refused. A provider without a cache ignores the markers and keeps the\n * order, so a request that is right for the caching adapter is right for\n * every adapter.\n *\n * The picture rule (AGL-2916) is enforced here too, and for the same reason:\n * a picture on an assistant turn, a picture for a model whose provider does\n * not say it reads one, or a picture past the bounds is refused before any\n * adapter is reached, so no door can send one a provider would reject.\n */\n\n/** The most `cacheBreakpoint` markers a request may carry. */\nexport const AI_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * The acceptable-use rules every generation prompt carries (AGL-2925).\n *\n * ONE constant, appended to each door's STATIC system text — inside the\n * cached prefix on the console chat, ahead of the mode prompt on the copy\n * assistant — so the rules the model is held to are the same at every\n * door and carry no per-org byte. The Free taste is what makes them worth\n * stating: a generated page that nobody paid for is the cheapest phishing\n * kit there is, and the model declining it is the first line, ahead of the\n * abuse-report route and the lockdown key.\n *\n * The categories are the platform's own Acceptable Use Policy, restated in\n * the second person for the model. A decline from the model's safety layer\n * arrives as a refusal, which the meter counts against the account's daily\n * pause and never against its credits.\n */\nexport const AI_ACCEPTABLE_USE_BLOCK = `Acceptable use — decline these rather than write them, and say briefly why:\n- Pages, emails or copy that harvest credentials, payment details or personal data by deception (phishing, fake sign-in or checkout pages, fake \"verify your account\" notices).\n- Impersonating a real business, brand, public body or person you were not told the user represents — their name, logo, sign-in page or voice — or copy that claims an endorsement, certification or partnership that was not given to you as a fact.\n- Content that promotes or sells prohibited goods and services: weapons, illegal drugs, counterfeit goods, malware or deceptive downloads, unlicensed gambling, or adult content involving minors.\n- Scams and fraud: fake investment or crypto schemes, advance-fee offers, tech-support scams, fake charities, deceptive \"you have won\" pages, or copy built to defraud.\n- Harassment, threats, or hateful content targeting a person or a group.\n- Spam farms: bulk near-duplicate pages or copy whose only purpose is to game search engines or send unsolicited mail.\nWhen a brief is ordinary business content, write it well and do not lecture.`\n\n/** A door's request, with the org's settings when the door knows the org. */\ninterface AiRuntimeOptions {\n /** The org's resolved `pluginSettings/ai`, for its provider and model choices. */\n settings?: AiPluginSettings\n /** A provider chosen by the caller, ahead of every setting; specs use it. */\n provider?: AiProvider\n}\n\nexport interface AiRequest extends AiRequestBase, AiRuntimeOptions {\n stream: false\n}\n\nexport interface AiStreamRequest extends AiRequestBase, AiRuntimeOptions {\n stream: true\n}\n\n/**\n * The cache guard, applied to the system blocks before any adapter sees\n * them. Exported so a spec and a door can assert a prompt's shape without\n * a provider.\n */\nexport function validateAiSystemBlocks(system: readonly AiSystemBlock[]): void {\n const breakpoints = system.filter((block) => block.cacheBreakpoint)\n if (breakpoints.length > AI_MAX_CACHE_BREAKPOINTS) {\n throw new AiRequestShapeError(\n `${breakpoints.length} cache breakpoints; at most ${AI_MAX_CACHE_BREAKPOINTS} are cached`,\n )\n }\n // Caching is a prefix match, so the LAST breakpoint decides what is\n // cached, and every block at or before it is inside the cached prefix. A\n // volatile block anywhere in that span — including one that is itself\n // the breakpoint — puts a per-request byte into the prefix (AGL-2352).\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n system.forEach((block, index) => {\n if (block.volatile && index <= lastBreakpoint) {\n throw new AiRequestShapeError(\n `system block ${index} is volatile but sits inside the cached prefix (last breakpoint at block ${lastBreakpoint})`,\n )\n }\n })\n // A site's block is cached per site (AGL-3661), so it closes the cached\n // span: a platform-wide breakpoint after it would be keyed on the site too,\n // and every workspace would warm its own copy of a shared prompt.\n const firstSite = system.findIndex((block) => block.site && block.cacheBreakpoint)\n if (firstSite >= 0 && firstSite !== lastBreakpoint) {\n throw new AiRequestShapeError(\n `system block ${firstSite} is a site's cached block but breakpoint ${lastBreakpoint} follows it`,\n )\n }\n}\n\n/**\n * Whether a provider's model reads a picture in a user turn (AGL-2916): what\n * the provider's own descriptor of the model says. A model the provider does\n * not describe reads none, so an adopter's provider is never sent a picture\n * it did not say it can take.\n */\nexport function aiModelReadsImages(provider: AiProvider, model: string): boolean {\n return provider\n .models()\n .some((descriptor) => descriptor.id === model && descriptor.capabilities.vision === true)\n}\n\n/**\n * Whether a model a door resolved reads pictures on the provider that would\n * serve the request: what a door asks before it attaches one, so a model that\n * does not is sent the text alone rather than refused.\n */\nexport function aiRouteReadsImages(model: string, settings?: AiPluginSettings): boolean {\n const provider = resolveAiProvider(settings)\n return provider ? aiModelReadsImages(provider, model) : false\n}\n\n/** A character standard base64 does not use, outside its padding. */\nconst NOT_BASE64 = /[^A-Za-z0-9+/]/\n\n/**\n * Whether `data` is standard base64 and nothing else: its alphabet, then at\n * most two `=` of padding; no `data:` prefix, no line breaks.\n *\n * Scanned for a character outside the alphabet rather than matched whole: a\n * picture is megabytes of base64, and a whole-string pattern with a greedy\n * loop can exhaust the regular expression engine's backtrack stack on input\n * that large, which throws a RangeError instead of answering.\n */\nfunction isBareBase64(data: string): boolean {\n const body = data.endsWith('==') ? data.slice(0, -2) : data.endsWith('=') ? data.slice(0, -1) : data\n return body.length > 0 && !NOT_BASE64.test(body)\n}\n\n/**\n * The picture guard (AGL-2916), applied to the messages before any adapter\n * sees them. A picture rides only a user turn, only to a model that reads\n * pictures, only in a format every such model takes, as bare base64 within\n * the size a provider accepts, and no more of them than a request may carry.\n * `readsImages` is asked only when a turn carries a picture. Exported so a\n * spec and a door can assert a request's shape without a provider.\n */\nexport function validateAiMessages(messages: readonly AiMessage[], readsImages: () => boolean): void {\n messages.forEach((message, index) => {\n if (typeof message.content === 'string') return\n if (!Array.isArray(message.content) || message.content.length === 0) {\n throw new AiRequestShapeError(`message ${index} has neither text nor parts`)\n }\n for (const part of message.content) {\n if (part.type === 'text') {\n if (typeof part.text !== 'string') {\n throw new AiRequestShapeError(`message ${index} has a text part with no text`)\n }\n continue\n }\n if (part.type !== 'image') {\n throw new AiRequestShapeError(`message ${index} has a part of an unknown type`)\n }\n if (message.role !== 'user') {\n throw new AiRequestShapeError(`message ${index} is an assistant turn and carries a picture`)\n }\n if (!(AI_IMAGE_MEDIA_TYPES as readonly string[]).includes(part.mediaType)) {\n throw new AiRequestShapeError(`message ${index} carries a picture of an unsupported type`)\n }\n if (typeof part.data !== 'string') {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n // The size first: it needs only the length, so an oversized picture is\n // refused without reading its bytes.\n if (aiBase64Bytes(part.data) > AI_IMAGE_MAX_BYTES) {\n throw new AiRequestShapeError(\n `message ${index} carries a picture over ${AI_IMAGE_MAX_BYTES} bytes`,\n )\n }\n if (!isBareBase64(part.data)) {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n }\n })\n const images = aiMessageImages(messages).length\n if (images > AI_REQUEST_MAX_IMAGES) {\n throw new AiRequestShapeError(`${images} pictures; a request carries at most ${AI_REQUEST_MAX_IMAGES}`)\n }\n if (images > 0 && !readsImages()) {\n throw new AiRequestShapeError('the request carries a picture and its model does not read pictures')\n }\n}\n\n/**\n * Characters to a token when a prompt is measured rather than tokenized\n * (AGL-2937). Four is the high end of English prose, so reading a prefix\n * through it errs LOW on the token count — and a prefix counts as cached\n * only when it clears its model's minimum even at that reading. Erring the\n * other way would let a prompt claim a cache it does not get.\n */\nexport const AI_CACHE_PREFIX_TOKEN_CHARS = 4\n\n/**\n * The characters inside the cached span of a request: the tools, which a\n * provider renders ahead of the system blocks and which therefore sit in\n * every prefix, plus every system block up to and including the last\n * breakpoint. Zero when the request marks no breakpoint at all.\n */\nexport function aiCachedPrefixChars(\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): number {\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n if (lastBreakpoint < 0) return 0\n const toolChars = tools.reduce((total, tool) => total + JSON.stringify(tool).length, 0)\n return system\n .slice(0, lastBreakpoint + 1)\n .reduce((total, block) => total + block.text.length, toolChars)\n}\n\n/**\n * Whether this request's cached span is long enough for the model to cache\n * it at all (AGL-2937). A prefix under the model's `cacheMinTokens` is\n * served with the breakpoints honored and nothing cached — no error, and a\n * usage report that reads as a permanent miss — so a door designed around a\n * cache has to answer this question before it believes its own markers, and\n * `runtime/ai-prompt-cache.spec.ts` answers it for every door at once.\n */\nexport function aiCachedPrefixCaches(\n model: string,\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): boolean {\n const chars = aiCachedPrefixChars(system, tools)\n return chars / AI_CACHE_PREFIX_TOKEN_CHARS >= aiModelCacheMinTokens(model)\n}\n\n/**\n * Whether a provider is configured to answer: registered, and its key\n * present. Every door asks this before it spends a reservation, and\n * answers 501 (or the docs fallback) when it is false.\n */\nexport function aiProviderReady(settings?: AiPluginSettings): boolean {\n return Boolean(resolveAiProvider(settings)?.readApiKey())\n}\n\n/** The provider that would answer, for a door that names it in its logs or its inventory. */\nexport function aiActiveProvider(settings?: AiPluginSettings): AiProvider | undefined {\n return resolveAiProvider(settings)\n}\n\nfunction providerFor(input: AiRuntimeOptions): { provider: AiProvider; apiKey: string } {\n const provider = input.provider ?? resolveAiProvider(input.settings)\n if (!provider) throw new Error('no AI provider is registered')\n const apiKey = provider.readApiKey()\n if (!apiKey) {\n // Every door gates on `aiProviderReady` before it gets here, so this is\n // a wiring fault rather than a customer path.\n throw new Error(`${provider.apiKeyEnv} is not set`)\n }\n return { provider, apiKey }\n}\n\n/**\n * Send one request through the resolved provider.\n *\n * Streaming: resolves once the provider has ACCEPTED the request — after\n * the headers, before the first token — with an iterator of simplified\n * events the caller wraps in its own wire format. A non-2xx answer rejects\n * with `AiUpstreamError` before the iterator exists, so a door can still\n * send a plain error status and hand back its reservation.\n *\n * Non-streaming: resolves with the parsed result, a decline typed as\n * `AiRefusal` rather than thrown.\n */\nexport async function runAiRequest(\n input: AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent>>\nexport async function runAiRequest(input: AiRequest): Promise<AiResult>\nexport async function runAiRequest(\n input: AiRequest | AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent> | AiResult> {\n validateAiSystemBlocks(input.system)\n const { provider, apiKey } = providerFor(input)\n validateAiMessages(input.messages, () => aiModelReadsImages(provider, input.model))\n const { stream, settings: _settings, provider: _provider, ...request } = input\n aiWarnIfReplayAskedInDeployment()\n const cacheTtl = request.cacheTtl ?? aiDevCacheTtl()\n const providerRequest: AiProviderRequest = { ...request, ...(cacheTtl ? { cacheTtl } : {}), apiKey }\n try {\n if (aiIsDeployedRuntime()) {\n return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest))\n }\n return await runInDevelopment(provider, providerRequest, stream)\n } catch (error) {\n await reportAccountProblem(provider, error)\n throw error\n }\n}\n\n/**\n * A request in a development process (AGL-3660): through the replay cache,\n * batch mode and the first-request-warms-the-prefix rule when this process\n * asked for them (`ai-dev-env.ts`), straight to the provider when it asked\n * for none. The file-backed half is loaded only when a layer is on, so a dev\n * server that never asked loads nothing it did not before.\n */\nasync function runInDevelopment(\n provider: AiProvider,\n request: AiProviderRequest,\n stream: boolean,\n): Promise<AsyncIterable<AiStreamEvent> | AiResult> {\n const mode = aiDevReplayMode()\n const batch = !stream && aiLiveBatchWanted() && Boolean(provider.completeBatch)\n if (mode === 'off' && !batch) {\n return stream ? provider.stream(request) : provider.complete(request)\n }\n const dev = await import('./ai-dev-replay')\n const ttlMs = request.cacheTtl === '1h' ? 60 * 60_000 : 5 * 60_000\n const prefix = dev.aiCachedPrefixKey(request)\n if (stream) {\n const live = () => dev.aiDevWarmPrefixFirst(prefix, ttlMs, () => provider.stream(request))\n return mode === 'off' ? live() : dev.aiDevReplayStream(provider, request, mode, live)\n }\n const live = async (): Promise<AiResult> => {\n const result = batch\n ? await aiCompleteInBatch(provider, request)\n : await dev.aiDevWarmPrefixFirst(prefix, ttlMs, () => provider.complete(request))\n dev.aiDevCountLive(result.usage, request.model, batch ? AI_BATCH_PRICE_FACTOR : 1)\n return result\n }\n return mode === 'off' ? live() : dev.aiDevReplayComplete(provider, request, mode, live)\n}\n\n/** What a batch's discount leaves of a request's price, for the development count. */\nconst AI_BATCH_PRICE_FACTOR = 0.5\n\n/**\n * Tells the operator when the failure is the platform's provider account —\n * a refused key, no credit — rather than the request (AGL-3377). Once per\n * provider and problem per window; loaded lazily so the runtime pulls no\n * Admin SDK until an account is actually broken. Never throws.\n */\nasync function reportAccountProblem(provider: AiProvider, error: unknown): Promise<void> {\n if (!(error instanceof AiUpstreamError) || !error.accountProblem) return\n try {\n const [{ raiseOperatorAlert }, { AI_PROVIDER_UNAVAILABLE }] = await Promise.all([\n import('./operator-alerts-seam'),\n import('../operator-alerts'),\n ])\n await raiseOperatorAlert(AI_PROVIDER_UNAVAILABLE, {\n dedupeKey: `${provider.id}:${error.accountProblem}`,\n context: {\n provider: provider.label,\n keyEnv: provider.apiKeyEnv,\n reason:\n error.accountProblem === 'credentials'\n ? `its API key was refused (HTTP ${error.status ?? 'unknown'})`\n : 'the account is out of credit',\n },\n })\n } catch (alertError) {\n console.error('[ai] the provider account alert could not be raised', alertError)\n }\n}\n"],"names":["aiModelCacheMinTokens","AI_IMAGE_MAX_BYTES","AI_IMAGE_MEDIA_TYPES","AI_REQUEST_MAX_IMAGES","AiRequestShapeError","AiUpstreamError","aiBase64Bytes","aiMessageImages","resolveAiProvider","aiDevCacheTtl","aiDevReplayMode","aiIsDeployedRuntime","aiLiveBatchWanted","aiWarnIfReplayAskedInDeployment","aiCompleteInBatch","AI_UPSTREAM_FAILURE_COPY","AI_MAX_CACHE_BREAKPOINTS","AI_ACCEPTABLE_USE_BLOCK","validateAiSystemBlocks","system","breakpoints","filter","block","cacheBreakpoint","length","lastBreakpoint","reduce","last","index","forEach","volatile","firstSite","findIndex","site","aiModelReadsImages","provider","model","models","some","descriptor","id","capabilities","vision","aiRouteReadsImages","settings","NOT_BASE64","isBareBase64","data","body","endsWith","slice","test","validateAiMessages","messages","readsImages","message","content","Array","isArray","part","type","text","role","includes","mediaType","images","AI_CACHE_PREFIX_TOKEN_CHARS","aiCachedPrefixChars","tools","toolChars","total","tool","JSON","stringify","aiCachedPrefixCaches","chars","aiProviderReady","Boolean","readApiKey","aiActiveProvider","providerFor","input","Error","apiKey","apiKeyEnv","runAiRequest","request","stream","_settings","_provider","cacheTtl","providerRequest","complete","runInDevelopment","error","reportAccountProblem","mode","batch","completeBatch","dev","ttlMs","prefix","aiCachedPrefixKey","live","aiDevWarmPrefixFirst","aiDevReplayStream","result","aiDevCountLive","usage","AI_BATCH_PRICE_FACTOR","aiDevReplayComplete","accountProblem","raiseOperatorAlert","AI_PROVIDER_UNAVAILABLE","Promise","all","dedupeKey","context","label","keyEnv","reason","status","alertError","console"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile';
18
+ import type { AiSiteContextInput } from '../model/ai-site-context';
19
+ import { type AiSitePreference, type AiSitePreferenceMatch } from '../model/ai-site-memory';
20
+ /**
21
+ * The server half of the site context (AGL-3661): the reads that make an
22
+ * `AiSiteContextInput`, the guided start's prefill and the memory writer.
23
+ * Every function takes the Firestore it works on, so a door, a step and a
24
+ * spec hand in their own.
25
+ *
26
+ * FAIL-SOFT on purpose. The context makes a job better; it never decides
27
+ * whether a job runs. A read that fails is logged and answered with `null`,
28
+ * and the job runs as it did before the context existed.
29
+ */
30
+ type Data = Record<string, unknown>;
31
+ /** The site's own preferences, read whole (they are capped at write). */
32
+ export declare function readAiSitePreferences(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<AiSitePreference[]>;
33
+ /**
34
+ * Everything the block says about one site, read in one window: the host,
35
+ * the site's profile, the workspace defaults and the remembered preferences.
36
+ * `null` for a site of another workspace (never read for it) and for any
37
+ * read that failed. A caller that already holds the host passes it.
38
+ */
39
+ export declare function readAiSiteContext(firestore: FirebaseFirestore.Firestore, input: {
40
+ orgId: string;
41
+ hostId: string | null | undefined;
42
+ host?: Data | null;
43
+ }): Promise<AiSiteContextInput | null>;
44
+ /**
45
+ * Fills the site's profile from the guided start's answers or a generator's
46
+ * output (AGL-3661), never over what the owner typed: the merge decides,
47
+ * inside a transaction so an owner's save in between is never overwritten.
48
+ * Returns whether anything was written.
49
+ */
50
+ export declare function prefillAiBusinessProfile(firestore: FirebaseFirestore.Firestore, hostId: string, values: BusinessProfileValues, source: 'start' | 'ai', now?: Date): Promise<boolean>;
51
+ /**
52
+ * Remembers the preferences one applied edit showed (AGL-3661): each one's
53
+ * count goes up, a preference of the same group that it replaces is
54
+ * forgotten, and the site keeps at most `AI_SITE_MEMORY_MAX`, dropping the
55
+ * least repeated and oldest first.
56
+ */
57
+ export declare function rememberAiSitePreferences(firestore: FirebaseFirestore.Firestore, hostId: string, matches: readonly AiSitePreferenceMatch[], now?: Date): Promise<void>;
58
+ export {};
@@ -0,0 +1,127 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { BUSINESS_PROFILE_SUBCOLLECTION, BUSINESS_PROFILE_SITE_DOC, BUSINESS_PROFILE_WORKSPACE_DOC, mergeBusinessProfilePrefill, resolveBusinessProfile } from "@aglyn/aglyn/app-utils/business-profile";
18
+ import { aiHostPublishContext } from "../model/ai-host-publish-context.js";
19
+ import { AI_SITE_MEMORY_SUBCOLLECTION, AI_SITE_MEMORY_MAX, aiListedPreferences, aiSitePreferenceOf } from "../model/ai-site-memory.js";
20
+ const docData = async (ref)=>{
21
+ var _snapshot_data;
22
+ const snapshot = await ref.get();
23
+ return snapshot.exists ? (_snapshot_data = snapshot.data()) != null ? _snapshot_data : null : null;
24
+ };
25
+ /** The site's own preferences, read whole (they are capped at write). */ export async function readAiSitePreferences(firestore, hostId) {
26
+ const snapshot = await firestore.collection('hosts').doc(hostId).collection(AI_SITE_MEMORY_SUBCOLLECTION).limit(AI_SITE_MEMORY_MAX * 2).get();
27
+ return snapshot.docs.map((row)=>aiSitePreferenceOf(row.id, row.data())).filter((row)=>row !== null);
28
+ }
29
+ /**
30
+ * Everything the block says about one site, read in one window: the host,
31
+ * the site's profile, the workspace defaults and the remembered preferences.
32
+ * `null` for a site of another workspace (never read for it) and for any
33
+ * read that failed. A caller that already holds the host passes it.
34
+ */ export async function readAiSiteContext(firestore, input) {
35
+ const { orgId } = input;
36
+ const hostId = typeof input.hostId === 'string' ? input.hostId.trim() : '';
37
+ if (!orgId || !hostId) return null;
38
+ try {
39
+ const hostRef = firestore.collection('hosts').doc(hostId);
40
+ const [host, site, workspace, preferences] = await Promise.all([
41
+ input.host !== undefined ? Promise.resolve(input.host) : docData(hostRef),
42
+ docData(hostRef.collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_SITE_DOC)),
43
+ docData(firestore.collection('orgs').doc(orgId).collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_WORKSPACE_DOC)),
44
+ readAiSitePreferences(firestore, hostId)
45
+ ]);
46
+ // A site of another workspace is never described to this one.
47
+ if (!host || host['orgId'] !== orgId) return null;
48
+ return {
49
+ profile: resolveBusinessProfile({
50
+ host: host,
51
+ site: site,
52
+ workspace: workspace
53
+ }),
54
+ publish: aiHostPublishContext(host),
55
+ preferences: aiListedPreferences(preferences)
56
+ };
57
+ } catch (error) {
58
+ console.warn('ai site context unread', {
59
+ orgId,
60
+ hostId,
61
+ error
62
+ });
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * Fills the site's profile from the guided start's answers or a generator's
68
+ * output (AGL-3661), never over what the owner typed: the merge decides,
69
+ * inside a transaction so an owner's save in between is never overwritten.
70
+ * Returns whether anything was written.
71
+ */ export async function prefillAiBusinessProfile(firestore, hostId, values, source, now = new Date()) {
72
+ const ref = firestore.collection('hosts').doc(hostId).collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_SITE_DOC);
73
+ return firestore.runTransaction(async (tx)=>{
74
+ const snapshot = await tx.get(ref);
75
+ const current = snapshot.exists ? snapshot.data() : null;
76
+ const next = mergeBusinessProfilePrefill(current, values, source);
77
+ if (!next) return false;
78
+ tx.set(ref, _extends({}, next, {
79
+ updatedAt: now,
80
+ updatedBy: null
81
+ }));
82
+ return true;
83
+ });
84
+ }
85
+ /**
86
+ * Remembers the preferences one applied edit showed (AGL-3661): each one's
87
+ * count goes up, a preference of the same group that it replaces is
88
+ * forgotten, and the site keeps at most `AI_SITE_MEMORY_MAX`, dropping the
89
+ * least repeated and oldest first.
90
+ */ export async function rememberAiSitePreferences(firestore, hostId, matches, now = new Date()) {
91
+ if (!matches.length) return;
92
+ const collection = firestore.collection('hosts').doc(hostId).collection(AI_SITE_MEMORY_SUBCOLLECTION);
93
+ const existing = await readAiSitePreferences(firestore, hostId);
94
+ const byId = new Map(existing.map((row)=>[
95
+ row.id,
96
+ row
97
+ ]));
98
+ const batch = firestore.batch();
99
+ const kept = new Map(existing.map((row)=>[
100
+ row.id,
101
+ row
102
+ ]));
103
+ for (const match of matches){
104
+ var _ref;
105
+ for (const row of existing){
106
+ if (row.group === match.group && row.id !== match.id) {
107
+ batch.delete(collection.doc(row.id));
108
+ kept.delete(row.id);
109
+ }
110
+ }
111
+ const held = byId.get(match.id);
112
+ const row = _extends({}, match, {
113
+ count: ((_ref = held == null ? void 0 : held.count) != null ? _ref : 0) + 1,
114
+ lastSeenAtMs: now.getTime(),
115
+ source: 'assist-edit'
116
+ });
117
+ kept.set(match.id, row);
118
+ batch.set(collection.doc(match.id), row);
119
+ }
120
+ const overflow = [
121
+ ...kept.values()
122
+ ].sort((a, b)=>b.count - a.count || b.lastSeenAtMs - a.lastSeenAtMs).slice(AI_SITE_MEMORY_MAX);
123
+ for (const row of overflow)batch.delete(collection.doc(row.id));
124
+ await batch.commit();
125
+ }
126
+
127
+ //# sourceMappingURL=site-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/site-context.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 {\n BUSINESS_PROFILE_SUBCOLLECTION,\n BUSINESS_PROFILE_SITE_DOC,\n BUSINESS_PROFILE_WORKSPACE_DOC,\n mergeBusinessProfilePrefill,\n resolveBusinessProfile,\n type BusinessProfileDoc,\n type BusinessProfileHost,\n type BusinessProfileValues,\n} from '@aglyn/aglyn/app-utils/business-profile'\nimport { aiHostPublishContext } from '../model/ai-host-publish-context'\nimport type { AiSiteContextInput } from '../model/ai-site-context'\nimport {\n AI_SITE_MEMORY_SUBCOLLECTION,\n AI_SITE_MEMORY_MAX,\n aiListedPreferences,\n aiSitePreferenceOf,\n type AiSitePreference,\n type AiSitePreferenceMatch,\n} from '../model/ai-site-memory'\n\n/**\n * The server half of the site context (AGL-3661): the reads that make an\n * `AiSiteContextInput`, the guided start's prefill and the memory writer.\n * Every function takes the Firestore it works on, so a door, a step and a\n * spec hand in their own.\n *\n * FAIL-SOFT on purpose. The context makes a job better; it never decides\n * whether a job runs. A read that fails is logged and answered with `null`,\n * and the job runs as it did before the context existed.\n */\n\ntype Data = Record<string, unknown>\n\nconst docData = async (ref: FirebaseFirestore.DocumentReference): Promise<Data | null> => {\n const snapshot = await ref.get()\n return snapshot.exists ? ((snapshot.data() ?? null) as Data | null) : null\n}\n\n/** The site's own preferences, read whole (they are capped at write). */\nexport async function readAiSitePreferences(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<AiSitePreference[]> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection(AI_SITE_MEMORY_SUBCOLLECTION)\n .limit(AI_SITE_MEMORY_MAX * 2)\n .get()\n return snapshot.docs\n .map((row) => aiSitePreferenceOf(row.id, row.data() as Data))\n .filter((row): row is AiSitePreference => row !== null)\n}\n\n/**\n * Everything the block says about one site, read in one window: the host,\n * the site's profile, the workspace defaults and the remembered preferences.\n * `null` for a site of another workspace (never read for it) and for any\n * read that failed. A caller that already holds the host passes it.\n */\nexport async function readAiSiteContext(\n firestore: FirebaseFirestore.Firestore,\n input: { orgId: string; hostId: string | null | undefined; host?: Data | null },\n): Promise<AiSiteContextInput | null> {\n const { orgId } = input\n const hostId = typeof input.hostId === 'string' ? input.hostId.trim() : ''\n if (!orgId || !hostId) return null\n try {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [host, site, workspace, preferences] = await Promise.all([\n input.host !== undefined ? Promise.resolve(input.host) : docData(hostRef),\n docData(hostRef.collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_SITE_DOC)),\n docData(\n firestore\n .collection('orgs')\n .doc(orgId)\n .collection(BUSINESS_PROFILE_SUBCOLLECTION)\n .doc(BUSINESS_PROFILE_WORKSPACE_DOC),\n ),\n readAiSitePreferences(firestore, hostId),\n ])\n // A site of another workspace is never described to this one.\n if (!host || host['orgId'] !== orgId) return null\n return {\n profile: resolveBusinessProfile({\n host: host as BusinessProfileHost,\n site: site as BusinessProfileDoc | null,\n workspace: workspace as BusinessProfileDoc | null,\n }),\n publish: aiHostPublishContext(host),\n preferences: aiListedPreferences(preferences),\n }\n } catch (error) {\n console.warn('ai site context unread', { orgId, hostId, error })\n return null\n }\n}\n\n/**\n * Fills the site's profile from the guided start's answers or a generator's\n * output (AGL-3661), never over what the owner typed: the merge decides,\n * inside a transaction so an owner's save in between is never overwritten.\n * Returns whether anything was written.\n */\nexport async function prefillAiBusinessProfile(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n values: BusinessProfileValues,\n source: 'start' | 'ai',\n now: Date = new Date(),\n): Promise<boolean> {\n const ref = firestore\n .collection('hosts')\n .doc(hostId)\n .collection(BUSINESS_PROFILE_SUBCOLLECTION)\n .doc(BUSINESS_PROFILE_SITE_DOC)\n return firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(ref)\n const current = snapshot.exists ? (snapshot.data() as BusinessProfileDoc) : null\n const next = mergeBusinessProfilePrefill(current, values, source)\n if (!next) return false\n tx.set(ref, { ...next, updatedAt: now, updatedBy: null })\n return true\n })\n}\n\n/**\n * Remembers the preferences one applied edit showed (AGL-3661): each one's\n * count goes up, a preference of the same group that it replaces is\n * forgotten, and the site keeps at most `AI_SITE_MEMORY_MAX`, dropping the\n * least repeated and oldest first.\n */\nexport async function rememberAiSitePreferences(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n matches: readonly AiSitePreferenceMatch[],\n now: Date = new Date(),\n): Promise<void> {\n if (!matches.length) return\n const collection = firestore.collection('hosts').doc(hostId).collection(AI_SITE_MEMORY_SUBCOLLECTION)\n const existing = await readAiSitePreferences(firestore, hostId)\n const byId = new Map(existing.map((row) => [row.id, row]))\n const batch = firestore.batch()\n const kept = new Map(existing.map((row) => [row.id, row]))\n for (const match of matches) {\n for (const row of existing) {\n if (row.group === match.group && row.id !== match.id) {\n batch.delete(collection.doc(row.id))\n kept.delete(row.id)\n }\n }\n const held = byId.get(match.id)\n const row: AiSitePreference = {\n ...match,\n count: (held?.count ?? 0) + 1,\n lastSeenAtMs: now.getTime(),\n source: 'assist-edit',\n }\n kept.set(match.id, row)\n batch.set(collection.doc(match.id), row)\n }\n const overflow = [...kept.values()]\n .sort((a, b) => b.count - a.count || b.lastSeenAtMs - a.lastSeenAtMs)\n .slice(AI_SITE_MEMORY_MAX)\n for (const row of overflow) batch.delete(collection.doc(row.id))\n await batch.commit()\n}\n"],"names":["BUSINESS_PROFILE_SUBCOLLECTION","BUSINESS_PROFILE_SITE_DOC","BUSINESS_PROFILE_WORKSPACE_DOC","mergeBusinessProfilePrefill","resolveBusinessProfile","aiHostPublishContext","AI_SITE_MEMORY_SUBCOLLECTION","AI_SITE_MEMORY_MAX","aiListedPreferences","aiSitePreferenceOf","docData","ref","snapshot","get","exists","data","readAiSitePreferences","firestore","hostId","collection","doc","limit","docs","map","row","id","filter","readAiSiteContext","input","orgId","trim","hostRef","host","site","workspace","preferences","Promise","all","undefined","resolve","profile","publish","error","console","warn","prefillAiBusinessProfile","values","source","now","Date","runTransaction","tx","current","next","set","updatedAt","updatedBy","rememberAiSitePreferences","matches","length","existing","byId","Map","batch","kept","match","group","delete","held","count","lastSeenAtMs","getTime","overflow","sort","a","b","slice","commit"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type StaffAiConversationKind, type StaffAiConversationsResponse } from '../usage/staff-org-ai-conversations';
18
+ type Firestore = FirebaseFirestore.Firestore;
19
+ /** One page of the org's conversations of `kind`. Exported for the spec. */
20
+ export declare function readStaffAiConversations(db: Firestore, orgId: string, kind: StaffAiConversationKind, after: string | null): Promise<StaffAiConversationsResponse>;
21
+ declare function handler(request: Request): Promise<Response>;
22
+ export { handler as GET };