@mvriu5/payload-ai 1.6.2 → 1.6.4

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 (126) hide show
  1. package/dist/components/action-toast/ActionToast.d.ts +1 -14
  2. package/dist/components/action-toast/ActionToast.js +1 -1
  3. package/dist/components/ai-input/AIInput.js +1 -1
  4. package/dist/components/audit-log-list/AuditLogList.d.ts +2 -3
  5. package/dist/components/diff-dialog/DiffDialog.d.ts +2 -6
  6. package/dist/components/diff-dialog/DiffDialog.js +1 -1
  7. package/dist/components/generate-field/GenerateField.js +2 -0
  8. package/dist/components/hooks/useAIChatStream.d.ts +1 -1
  9. package/dist/components/hooks/useAISettings.d.ts +1 -1
  10. package/dist/components/hooks/useAISettings.js +1 -1
  11. package/dist/components/hooks/useDocumentMentionSuggestions.js +1 -1
  12. package/dist/components/hooks/useMentions.js +1 -1
  13. package/dist/components/hooks/usePluginConfig.d.ts +6 -6
  14. package/dist/components/hooks/usePluginConfig.js +1 -1
  15. package/dist/{payload → features}/collectionPermissions.js +1 -1
  16. package/dist/{payload/textFieldGeneration.d.ts → features/content/fieldGeneration.d.ts} +4 -0
  17. package/dist/{payload/textFieldGeneration.js → features/content/fieldGeneration.js} +19 -3
  18. package/dist/{ai → features}/promptCaching.d.ts +2 -2
  19. package/dist/features/proposals/containerNormalization.d.ts +8 -0
  20. package/dist/features/proposals/containerNormalization.js +112 -0
  21. package/dist/{payload/proposalData.d.ts → features/proposals/data.d.ts} +5 -10
  22. package/dist/features/proposals/data.js +113 -0
  23. package/dist/features/proposals/fieldNormalization.d.ts +2 -0
  24. package/dist/features/proposals/fieldNormalization.js +69 -0
  25. package/dist/features/proposals/normalizationTypes.d.ts +31 -0
  26. package/dist/features/proposals/normalizationTypes.js +1 -0
  27. package/dist/features/proposals/optionInference.d.ts +13 -0
  28. package/dist/features/proposals/optionInference.js +46 -0
  29. package/dist/features/proposals/recordNormalization.d.ts +6 -0
  30. package/dist/features/proposals/recordNormalization.js +78 -0
  31. package/dist/features/proposals/relationshipNormalization.d.ts +8 -0
  32. package/dist/features/proposals/relationshipNormalization.js +71 -0
  33. package/dist/{ai/proposalRepair.d.ts → features/proposals/repair.d.ts} +1 -1
  34. package/dist/{ai/proposalSigning.d.ts → features/proposals/signing.d.ts} +2 -5
  35. package/dist/{ai/proposalSigning.js → features/proposals/signing.js} +1 -1
  36. package/dist/features/proposals/target.d.ts +52 -0
  37. package/dist/features/proposals/target.js +125 -0
  38. package/dist/features/proposals/types.d.ts +49 -0
  39. package/dist/features/proposals/types.js +1 -0
  40. package/dist/features/proposals/validation.d.ts +12 -0
  41. package/dist/features/proposals/validation.js +26 -0
  42. package/dist/features/providers/requestContext.d.ts +53 -0
  43. package/dist/features/providers/requestContext.js +90 -0
  44. package/dist/{ai/providerRuntime.d.ts → features/providers/runtime.d.ts} +1 -1
  45. package/dist/{ai/providerRuntime.js → features/providers/runtime.js} +1 -1
  46. package/dist/features/schema/context.d.ts +7 -0
  47. package/dist/features/schema/context.js +11 -0
  48. package/dist/{payload/schemaContext.d.ts → features/schema/description.d.ts} +6 -33
  49. package/dist/{payload/schemaContext.js → features/schema/description.js} +3 -207
  50. package/dist/{payload/toolFieldSelection.d.ts → features/schema/fieldSelection.d.ts} +1 -1
  51. package/dist/{payload/toolFieldSelection.js → features/schema/fieldSelection.js} +2 -2
  52. package/dist/features/schema/mentionDocuments.d.ts +15 -0
  53. package/dist/features/schema/mentionDocuments.js +50 -0
  54. package/dist/features/schema/mentions.d.ts +18 -0
  55. package/dist/features/schema/mentions.js +100 -0
  56. package/dist/{payload/normalizeData.js → features/schema/normalize.js} +1 -1
  57. package/dist/features/schema/registry.d.ts +30 -0
  58. package/dist/features/schema/registry.js +73 -0
  59. package/dist/{payload → features/schema}/toolSchemas.d.ts +1 -1
  60. package/dist/{payload → features/schema}/toolSchemas.js +1 -1
  61. package/dist/{ai → features}/sensitiveData.js +1 -1
  62. package/dist/{ai → features}/tokenUsage.d.ts +1 -2
  63. package/dist/handlers/apply/audit.d.ts +42 -0
  64. package/dist/handlers/apply/audit.js +150 -0
  65. package/dist/handlers/apply/operations.d.ts +18 -0
  66. package/dist/handlers/apply/operations.js +377 -0
  67. package/dist/handlers/apply/types.d.ts +33 -0
  68. package/dist/handlers/apply/types.js +1 -0
  69. package/dist/handlers/apply/validation.d.ts +45 -0
  70. package/dist/handlers/apply/validation.js +125 -0
  71. package/dist/handlers/applyActionHandler.d.ts +1 -6
  72. package/dist/handlers/applyActionHandler.js +13 -757
  73. package/dist/handlers/auditLogHandler.js +4 -8
  74. package/dist/handlers/chat/intent.d.ts +32 -0
  75. package/dist/handlers/chat/intent.js +155 -0
  76. package/dist/handlers/chat/prompt.d.ts +20 -0
  77. package/dist/handlers/chat/prompt.js +55 -0
  78. package/dist/handlers/chat/proposalTools.d.ts +96 -0
  79. package/dist/handlers/chat/proposalTools.js +451 -0
  80. package/dist/handlers/chat/streaming.d.ts +38 -0
  81. package/dist/handlers/chat/streaming.js +136 -0
  82. package/dist/handlers/chat/toolFactory.d.ts +79 -0
  83. package/dist/handlers/chat/toolFactory.js +156 -0
  84. package/dist/handlers/chat/types.d.ts +57 -0
  85. package/dist/handlers/chat/types.js +1 -0
  86. package/dist/handlers/chatHandler.d.ts +1 -44
  87. package/dist/handlers/chatHandler.js +186 -1162
  88. package/dist/handlers/generateFieldHandler.d.ts +3 -8
  89. package/dist/handlers/generateFieldHandler.js +71 -99
  90. package/dist/handlers/http.d.ts +17 -0
  91. package/dist/handlers/http.js +21 -0
  92. package/dist/handlers/mediaUploadHandler.js +12 -34
  93. package/dist/handlers/mentionSuggestionHandler.d.ts +1 -1
  94. package/dist/handlers/mentionSuggestionHandler.js +6 -10
  95. package/dist/handlers/proposalDiffHandler.d.ts +1 -1
  96. package/dist/handlers/proposalDiffHandler.js +72 -195
  97. package/dist/handlers/translateDocumentHandler.d.ts +3 -8
  98. package/dist/handlers/translateDocumentHandler.js +26 -82
  99. package/dist/index.d.ts +4 -30
  100. package/dist/index.js +31 -421
  101. package/dist/plugin/admin.d.ts +30 -0
  102. package/dist/plugin/admin.js +155 -0
  103. package/dist/plugin/collections.d.ts +3 -0
  104. package/dist/plugin/collections.js +169 -0
  105. package/dist/plugin/endpoints.d.ts +23 -0
  106. package/dist/plugin/endpoints.js +84 -0
  107. package/dist/plugin/options.d.ts +4 -0
  108. package/dist/plugin/options.js +13 -0
  109. package/dist/plugin/types.d.ts +28 -0
  110. package/dist/plugin/types.js +1 -0
  111. package/dist/{payload/shared.d.ts → utils/data.d.ts} +2 -12
  112. package/package.json +1 -1
  113. package/dist/payload/proposalData.js +0 -578
  114. /package/dist/{payload → features}/collectionPermissions.d.ts +0 -0
  115. /package/dist/{payload → features/content}/documentTranslation.d.ts +0 -0
  116. /package/dist/{payload → features/content}/documentTranslation.js +0 -0
  117. /package/dist/{ai → features}/promptCaching.js +0 -0
  118. /package/dist/{ai/proposalRepair.js → features/proposals/repair.js} +0 -0
  119. /package/dist/{ai/providerOptions.d.ts → features/providers/options.d.ts} +0 -0
  120. /package/dist/{ai/providerOptions.js → features/providers/options.js} +0 -0
  121. /package/dist/{payload/normalizeData.d.ts → features/schema/normalize.d.ts} +0 -0
  122. /package/dist/{ai → features}/sensitiveData.d.ts +0 -0
  123. /package/dist/{ai → features}/tokenUsage.js +0 -0
  124. /package/dist/{payload/shared.js → utils/data.js} +0 -0
  125. /package/dist/{payload → utils}/logging.d.ts +0 -0
  126. /package/dist/{payload → utils}/logging.js +0 -0
@@ -1,285 +1,12 @@
1
- import { verifyActionProposal } from "../ai/proposalSigning.js";
2
- import { containsSensitiveData, redactSensitiveData } from "../ai/sensitiveData.js";
3
- import { getSchemaFields } from "../payload/normalizeData.js";
4
- import { applyLocalizedRequiredFallbackToPreparedData, prepareProposalWriteData } from "../payload/proposalData.js";
5
- import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
6
- import { getDefaultLocale, getJSONLineKey, getOptionalNumber, hasLocalizedData, isActionProposal, isKnownGlobal, isRecord, mergeData } from "../payload/shared.js";
7
- import { getLogPreview, logHandlerEvent } from "../payload/logging.js";
8
- const getProposalLogSummary = (proposal)=>({
9
- action: proposal.action,
10
- collection: "collection" in proposal ? proposal.collection : undefined,
11
- hasData: "data" in proposal && Boolean(proposal.data),
12
- hasLocalizedData: "localizedData" in proposal && Boolean(proposal.localizedData),
13
- id: "id" in proposal ? proposal.id : undefined,
14
- label: proposal.label,
15
- locale: proposal.locale,
16
- locales: "localizedData" in proposal && proposal.localizedData ? Object.keys(proposal.localizedData) : undefined,
17
- slug: "slug" in proposal ? proposal.slug : undefined
18
- });
19
- const getProposalMeta = (proposal)=>{
20
- if (!proposal) return {};
21
- return {
22
- action: proposal.action,
23
- collection: "collection" in proposal ? proposal.collection : undefined,
24
- id: "id" in proposal ? proposal.id : undefined,
25
- slug: "slug" in proposal ? proposal.slug : undefined
26
- };
27
- };
28
- const createApplyDebugPayload = ({ details, phase, proposal, reason })=>({
29
- ...getProposalMeta(proposal),
30
- details,
31
- phase,
32
- reason
33
- });
34
- const getAppliedDocReference = (doc)=>{
35
- return doc?.id === undefined ? undefined : {
36
- id: doc.id
37
- };
38
- };
39
- const isAllowedCollection = (req, collection, collections, action = "read")=>{
40
- return isCollectionActionAllowed({
41
- action,
42
- permissions: collections,
43
- req,
44
- slug: collection
45
- });
46
- };
47
- const countDiffChanges = ({ after, before })=>{
48
- const beforeLines = JSON.stringify(before, null, 2).split("\n");
49
- const afterLines = JSON.stringify(after, null, 2).split("\n");
50
- const dp = Array.from({
51
- length: beforeLines.length + 1
52
- }, ()=>Array(afterLines.length + 1).fill(0));
53
- let additions = 0;
54
- let removals = 0;
55
- for(let i = beforeLines.length - 1; i >= 0; i -= 1){
56
- for(let j = afterLines.length - 1; j >= 0; j -= 1){
57
- dp[i][j] = beforeLines[i] === afterLines[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
58
- }
59
- }
60
- let beforeIndex = 0;
61
- let afterIndex = 0;
62
- while(beforeIndex < beforeLines.length && afterIndex < afterLines.length){
63
- if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
64
- beforeIndex += 1;
65
- afterIndex += 1;
66
- continue;
67
- }
68
- const beforeKey = getJSONLineKey(beforeLines[beforeIndex]);
69
- const afterKey = getJSONLineKey(afterLines[afterIndex]);
70
- if (beforeKey && beforeKey === afterKey) {
71
- removals += 1;
72
- additions += 1;
73
- beforeIndex += 1;
74
- afterIndex += 1;
75
- continue;
76
- }
77
- if (dp[beforeIndex + 1][afterIndex] >= dp[beforeIndex][afterIndex + 1]) {
78
- removals += 1;
79
- beforeIndex += 1;
80
- } else {
81
- additions += 1;
82
- afterIndex += 1;
83
- }
84
- }
85
- removals += beforeLines.length - beforeIndex;
86
- additions += afterLines.length - afterIndex;
87
- return {
88
- additions,
89
- removals
90
- };
91
- };
92
- const getTargetURL = ({ documentID, proposal, req })=>{
93
- const adminRoute = req.payload.config.routes.admin || "/admin";
94
- if (proposal.action === "updateGlobal") return `${adminRoute}/globals/${proposal.slug}`;
95
- if (proposal.action === "delete") return null;
96
- const id = proposal.action === "create" ? documentID : proposal.id;
97
- if (!id) return null;
98
- return `${adminRoute}/collections/${proposal.collection}/${id}`;
99
- };
100
- const getUserID = (req)=>{
101
- const user = req.user;
102
- return user?.id === undefined ? undefined : String(user.id);
103
- };
104
- const getUserLabel = (req)=>{
105
- const user = req.user;
106
- if (typeof user?.email === "string" && user.email) return user.email;
107
- if (typeof user?.name === "string" && user.name) return user.name;
108
- if (typeof user?.username === "string" && user.username) return user.username;
109
- if (user?.id !== undefined) return String(user.id);
110
- return null;
111
- };
112
- const logAIChange = async ({ changeLogCollection, context, target, proposal, req })=>{
113
- if (!changeLogCollection) return null;
114
- const before = redactSensitiveData(target.before);
115
- const after = redactSensitiveData(target.after);
116
- const { additions, removals } = countDiffChanges({
117
- after,
118
- before
119
- });
120
- const proposalForLog = {
121
- ...proposal
122
- };
123
- delete proposalForLog._aiSignature;
124
- try {
125
- await req.payload.create({
126
- collection: changeLogCollection,
127
- data: {
128
- action: proposal.action,
129
- additions,
130
- after,
131
- aiResponse: context?.aiResponse,
132
- before,
133
- collection: "collection" in proposal ? proposal.collection : undefined,
134
- documentID: target.documentID === undefined ? undefined : String(target.documentID),
135
- inputTokens: context?.tokenUsage?.inputTokens,
136
- outputTokens: context?.tokenUsage?.outputTokens,
137
- prompt: context?.prompt,
138
- proposal: redactSensitiveData(proposalForLog),
139
- removals,
140
- slug: "slug" in proposal ? proposal.slug : undefined,
141
- totalTokens: context?.tokenUsage?.totalTokens,
142
- targetType: proposal.action === "updateGlobal" ? "global" : "collection",
143
- targetURL: getTargetURL({
144
- documentID: target.documentID,
145
- proposal,
146
- req
147
- }),
148
- title: proposal.label,
149
- userID: getUserID(req),
150
- userLabel: getUserLabel(req)
151
- },
152
- overrideAccess: true,
153
- req
154
- });
155
- return {
156
- action: proposal.action,
157
- additions,
158
- after,
159
- aiResponse: context?.aiResponse || null,
160
- before,
161
- collection: "collection" in proposal ? proposal.collection : null,
162
- createdAt: new Date().toISOString(),
163
- documentID: target.documentID === undefined ? null : String(target.documentID),
164
- inputTokens: context?.tokenUsage?.inputTokens ?? null,
165
- outputTokens: context?.tokenUsage?.outputTokens ?? null,
166
- prompt: context?.prompt || null,
167
- removals,
168
- slug: "slug" in proposal ? proposal.slug : null,
169
- totalTokens: context?.tokenUsage?.totalTokens ?? null,
170
- targetType: proposal.action === "updateGlobal" ? "global" : "collection",
171
- title: proposal.label,
172
- userID: getUserID(req) || null,
173
- userLabel: getUserLabel(req),
174
- url: getTargetURL({
175
- documentID: target.documentID,
176
- proposal,
177
- req
178
- })
179
- };
180
- } catch (err) {
181
- req.payload.logger.error({
182
- err,
183
- msg: "AI change log entry could not be written",
184
- proposal: getProposalMeta(proposal)
185
- });
186
- return null;
187
- }
188
- };
1
+ import { getLogPreview, logHandlerEvent } from "../utils/logging.js";
2
+ import { isRecord } from "../utils/data.js";
3
+ import { applyActionProposal } from "./apply/operations.js";
4
+ import { createApplyDebugPayload, createApplyFailureResponse, getProposalLogSummary, getProposalMeta, resolveApplyRequest } from "./apply/validation.js";
189
5
  export const createApplyActionHandler = (options = {})=>async (req)=>{
190
- const failApply = ({ debug, error, logMessage, proposal, status = 400 })=>{
191
- logHandlerEvent(req, "warn", {
192
- debug,
193
- msg: logMessage,
194
- proposal: getProposalMeta(proposal)
195
- });
196
- return Response.json({
197
- debug,
198
- error
199
- }, {
200
- status
201
- });
202
- };
203
- if (!req.user) {
204
- return failApply({
205
- debug: createApplyDebugPayload({
206
- phase: "authorization",
207
- reason: "unauthorized"
208
- }),
209
- error: "Unauthorized",
210
- logMessage: "AI apply blocked: unauthorized request",
211
- status: 401
212
- });
213
- }
214
- const body = req.json ? await req.json().catch(()=>null) : null;
215
- const proposal = body?.proposal;
216
- if (!proposal) {
217
- return failApply({
218
- debug: createApplyDebugPayload({
219
- phase: "apply_validation",
220
- reason: "missing_proposal"
221
- }),
222
- error: "Proposal is required",
223
- logMessage: "AI apply blocked: missing proposal payload"
224
- });
225
- }
226
- if (!verifyActionProposal(proposal)) {
227
- return failApply({
228
- debug: createApplyDebugPayload({
229
- phase: "apply_validation",
230
- proposal,
231
- reason: "invalid_signature"
232
- }),
233
- error: "Proposal signature is invalid or expired.",
234
- logMessage: "AI apply blocked: invalid proposal signature",
235
- proposal
236
- });
237
- }
238
- if (!isActionProposal(proposal)) {
239
- return failApply({
240
- debug: createApplyDebugPayload({
241
- phase: "apply_validation",
242
- proposal,
243
- reason: "invalid_proposal_shape"
244
- }),
245
- error: "Proposal is invalid.",
246
- logMessage: "AI apply blocked: invalid proposal shape",
247
- proposal
248
- });
249
- }
250
- if ("data" in proposal && proposal.data && containsSensitiveData(proposal.data)) {
251
- return failApply({
252
- debug: createApplyDebugPayload({
253
- phase: "apply_validation",
254
- proposal,
255
- reason: "sensitive_data_in_data"
256
- }),
257
- error: "Proposal contains sensitive fields and cannot be applied.",
258
- logMessage: "AI apply blocked: sensitive data detected in proposal data",
259
- proposal
260
- });
261
- }
262
- if (hasLocalizedData(proposal) && Object.values(proposal.localizedData).some((value)=>containsSensitiveData(value))) {
263
- return failApply({
264
- debug: createApplyDebugPayload({
265
- phase: "apply_validation",
266
- proposal,
267
- reason: "sensitive_data_in_localized_data"
268
- }),
269
- error: "Proposal contains sensitive fields and cannot be applied.",
270
- logMessage: "AI apply blocked: sensitive data detected in localized proposal data",
271
- proposal
272
- });
273
- }
274
- const logContext = {
275
- aiResponse: typeof body?.aiResponse === "string" && body.aiResponse.trim() ? body.aiResponse.trim() : undefined,
276
- prompt: typeof body?.prompt === "string" && body.prompt.trim() ? body.prompt.trim() : undefined,
277
- tokenUsage: body?.tokenUsage ? {
278
- inputTokens: getOptionalNumber(body.tokenUsage.inputTokens),
279
- outputTokens: getOptionalNumber(body.tokenUsage.outputTokens),
280
- totalTokens: getOptionalNumber(body.tokenUsage.totalTokens)
281
- } : undefined
282
- };
6
+ const resolution = await resolveApplyRequest(req);
7
+ if (!resolution.ok) return createApplyFailureResponse(req, resolution.failure);
8
+ const { context: logContext, proposal } = resolution;
9
+ const failApply = (failure)=>createApplyFailureResponse(req, failure);
283
10
  logHandlerEvent(req, "info", {
284
11
  msg: "AI apply started",
285
12
  promptPreview: getLogPreview(logContext.prompt),
@@ -287,483 +14,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
287
14
  tokenUsage: logContext.tokenUsage
288
15
  });
289
16
  try {
290
- const inferenceText = logContext.prompt;
291
- const defaultLocale = getDefaultLocale(req);
292
- if (proposal.action === "updateGlobal") {
293
- if (!isKnownGlobal(req, proposal.slug)) {
294
- return failApply({
295
- debug: createApplyDebugPayload({
296
- phase: "apply_validation",
297
- proposal,
298
- reason: "unknown_global"
299
- }),
300
- error: "Unknown global",
301
- logMessage: "AI apply blocked: unknown global",
302
- proposal
303
- });
304
- }
305
- const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
306
- if (hasLocalizedData(proposal)) {
307
- const beforeByLocale = {};
308
- const afterByLocale = {};
309
- const globalFields = getSchemaFields({
310
- fields: globalConfig?.fields || [],
311
- slug: proposal.slug
312
- });
313
- const preparedData = prepareProposalWriteData({
314
- collectionConfig: {
315
- fields: globalConfig?.fields || [],
316
- slug: proposal.slug
317
- },
318
- inferenceText,
319
- label: proposal.label,
320
- localizedData: proposal.localizedData,
321
- mode: "update"
322
- });
323
- if (preparedData.issues.length > 0 || !preparedData.localizedData) {
324
- return failApply({
325
- debug: createApplyDebugPayload({
326
- details: {
327
- issues: preparedData.issues
328
- },
329
- phase: "apply_validation",
330
- proposal,
331
- reason: "invalid_global_write_shape"
332
- }),
333
- error: "Proposal is invalid.",
334
- logMessage: "AI apply blocked: invalid global proposal data",
335
- proposal
336
- });
337
- }
338
- const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
339
- depth: 2,
340
- fallbackLocale: false,
341
- locale: defaultLocale,
342
- req,
343
- slug: proposal.slug
344
- }) : null;
345
- const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
346
- const before = await req.payload.findGlobal({
347
- depth: 2,
348
- fallbackLocale: false,
349
- locale,
350
- req,
351
- slug: proposal.slug
352
- });
353
- const completedData = applyLocalizedRequiredFallbackToPreparedData({
354
- fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
355
- fields: globalFields,
356
- preparedData: localeData
357
- });
358
- await req.payload.updateGlobal({
359
- data: completedData,
360
- locale,
361
- overrideAccess: false,
362
- req,
363
- slug: proposal.slug
364
- });
365
- return {
366
- before,
367
- completedData,
368
- locale
369
- };
370
- }));
371
- localizedResults.forEach(({ before, completedData, locale })=>{
372
- beforeByLocale[locale] = before;
373
- afterByLocale[locale] = mergeData(before, completedData);
374
- });
375
- const change = await logAIChange({
376
- changeLogCollection: options.changeLogCollection,
377
- context: logContext,
378
- proposal,
379
- req,
380
- target: {
381
- after: afterByLocale,
382
- before: beforeByLocale
383
- }
384
- });
385
- logHandlerEvent(req, "info", {
386
- changeLogged: Boolean(change),
387
- locales: Object.keys(proposal.localizedData),
388
- msg: "AI apply succeeded",
389
- proposal: getProposalLogSummary(proposal)
390
- });
391
- return Response.json({
392
- change,
393
- doc: undefined,
394
- status: "applied"
395
- });
396
- }
397
- const preparedData = prepareProposalWriteData({
398
- collectionConfig: {
399
- fields: globalConfig?.fields || [],
400
- slug: proposal.slug
401
- },
402
- data: proposal.data,
403
- inferenceText,
404
- label: proposal.label,
405
- mode: "update"
406
- });
407
- if (preparedData.issues.length > 0 || !preparedData.data) {
408
- return failApply({
409
- debug: createApplyDebugPayload({
410
- details: {
411
- issues: preparedData.issues
412
- },
413
- phase: "apply_validation",
414
- proposal,
415
- reason: "invalid_global_write_shape"
416
- }),
417
- error: "Proposal is invalid.",
418
- logMessage: "AI apply blocked: invalid global proposal data",
419
- proposal
420
- });
421
- }
422
- const before = await req.payload.findGlobal({
423
- depth: 2,
424
- ...proposal.locale ? {
425
- locale: proposal.locale
426
- } : {},
427
- req,
428
- slug: proposal.slug
429
- });
430
- const doc = await req.payload.updateGlobal({
431
- data: preparedData.data,
432
- ...proposal.locale ? {
433
- locale: proposal.locale
434
- } : {},
435
- overrideAccess: false,
436
- req,
437
- slug: proposal.slug
438
- });
439
- const change = await logAIChange({
440
- changeLogCollection: options.changeLogCollection,
441
- context: logContext,
442
- proposal,
443
- req,
444
- target: {
445
- after: mergeData(before, preparedData.data),
446
- before
447
- }
448
- });
449
- logHandlerEvent(req, "info", {
450
- changeLogged: Boolean(change),
451
- locale: proposal.locale,
452
- msg: "AI apply succeeded",
453
- proposal: getProposalLogSummary(proposal)
454
- });
455
- return Response.json({
456
- change,
457
- doc: getAppliedDocReference(doc),
458
- status: "applied"
459
- });
460
- }
461
- if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) {
462
- return failApply({
463
- debug: createApplyDebugPayload({
464
- phase: "authorization",
465
- proposal,
466
- reason: "unknown_or_disallowed_collection"
467
- }),
468
- error: "Unknown collection",
469
- logMessage: "AI apply blocked: unknown or disallowed collection",
470
- proposal
471
- });
472
- }
473
- if (proposal.action === "delete") {
474
- const doc = await req.payload.delete({
475
- collection: proposal.collection,
476
- id: proposal.id,
477
- overrideAccess: false,
478
- req
479
- });
480
- const change = await logAIChange({
481
- changeLogCollection: options.changeLogCollection,
482
- context: logContext,
483
- proposal,
484
- req,
485
- target: {
486
- after: {},
487
- before: doc,
488
- documentID: proposal.id
489
- }
490
- });
491
- logHandlerEvent(req, "info", {
492
- changeLogged: Boolean(change),
493
- documentID: proposal.id,
494
- msg: "AI apply succeeded",
495
- proposal: getProposalLogSummary(proposal)
496
- });
497
- return Response.json({
498
- change,
499
- doc: getAppliedDocReference(doc),
500
- status: "applied"
501
- });
502
- }
503
- const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
504
- const collectionFields = getSchemaFields(collectionConfig);
505
- if (hasLocalizedData(proposal)) {
506
- const preparedData = prepareProposalWriteData({
507
- collectionConfig,
508
- inferenceText,
509
- label: proposal.label,
510
- localizedData: proposal.localizedData,
511
- mode: proposal.action
512
- });
513
- if (preparedData.issues.length > 0 || !preparedData.localizedData) {
514
- return failApply({
515
- debug: createApplyDebugPayload({
516
- details: {
517
- issues: preparedData.issues
518
- },
519
- phase: "apply_validation",
520
- proposal,
521
- reason: "invalid_collection_write_shape"
522
- }),
523
- error: "Proposal is invalid.",
524
- logMessage: "AI apply blocked: invalid collection proposal data",
525
- proposal
526
- });
527
- }
528
- if (proposal.action === "create") {
529
- const localeEntries = Object.entries(preparedData.localizedData);
530
- const [firstLocale, firstLocaleData] = localeEntries[0] || [];
531
- if (!firstLocale) {
532
- return failApply({
533
- debug: createApplyDebugPayload({
534
- phase: "apply_validation",
535
- proposal,
536
- reason: "localized_create_without_locales"
537
- }),
538
- error: "Proposal is invalid.",
539
- logMessage: "AI apply blocked: localized create proposal has no locales",
540
- proposal
541
- });
542
- }
543
- const doc = await req.payload.create({
544
- collection: proposal.collection,
545
- data: firstLocaleData,
546
- locale: firstLocale,
547
- overrideAccess: false,
548
- req
549
- });
550
- const beforeByLocale = {
551
- [firstLocale]: {}
552
- };
553
- const afterByLocale = {
554
- [firstLocale]: doc
555
- };
556
- const fallbackSource = doc;
557
- const localizedResults = await Promise.all(localeEntries.slice(1).map(async ([locale, localeData])=>{
558
- const completedData = applyLocalizedRequiredFallbackToPreparedData({
559
- fallbackSource,
560
- fields: collectionFields,
561
- preparedData: localeData
562
- });
563
- await req.payload.update({
564
- collection: proposal.collection,
565
- data: completedData,
566
- id: String(doc.id),
567
- locale,
568
- overrideAccess: false,
569
- req
570
- });
571
- return {
572
- completedData,
573
- locale
574
- };
575
- }));
576
- localizedResults.forEach(({ completedData, locale })=>{
577
- beforeByLocale[locale] = {};
578
- afterByLocale[locale] = completedData;
579
- });
580
- const change = await logAIChange({
581
- changeLogCollection: options.changeLogCollection,
582
- context: logContext,
583
- proposal,
584
- req,
585
- target: {
586
- after: afterByLocale,
587
- before: beforeByLocale,
588
- documentID: doc.id
589
- }
590
- });
591
- logHandlerEvent(req, "info", {
592
- changeLogged: Boolean(change),
593
- documentID: doc.id,
594
- locales: localeEntries.map(([locale])=>locale),
595
- msg: "AI apply succeeded",
596
- proposal: getProposalLogSummary(proposal)
597
- });
598
- return Response.json({
599
- change,
600
- doc: getAppliedDocReference(doc),
601
- status: "applied"
602
- });
603
- }
604
- const beforeByLocale = {};
605
- const afterByLocale = {};
606
- const defaultLocaleDoc = defaultLocale ? await req.payload.findByID({
607
- collection: proposal.collection,
608
- depth: 2,
609
- fallbackLocale: false,
610
- id: proposal.id,
611
- locale: defaultLocale,
612
- req
613
- }) : null;
614
- const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
615
- const before = await req.payload.findByID({
616
- collection: proposal.collection,
617
- depth: 2,
618
- fallbackLocale: false,
619
- id: proposal.id,
620
- locale,
621
- req
622
- });
623
- const completedData = applyLocalizedRequiredFallbackToPreparedData({
624
- fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
625
- fields: collectionFields,
626
- preparedData: localeData
627
- });
628
- await req.payload.update({
629
- collection: proposal.collection,
630
- data: completedData,
631
- id: proposal.id,
632
- locale,
633
- overrideAccess: false,
634
- req
635
- });
636
- return {
637
- before,
638
- completedData,
639
- locale
640
- };
641
- }));
642
- localizedResults.forEach(({ before, completedData, locale })=>{
643
- beforeByLocale[locale] = before;
644
- afterByLocale[locale] = mergeData(before, completedData);
645
- });
646
- const change = await logAIChange({
647
- changeLogCollection: options.changeLogCollection,
648
- context: logContext,
649
- proposal,
650
- req,
651
- target: {
652
- after: afterByLocale,
653
- before: beforeByLocale,
654
- documentID: proposal.id
655
- }
656
- });
657
- logHandlerEvent(req, "info", {
658
- changeLogged: Boolean(change),
659
- documentID: proposal.id,
660
- locales: Object.keys(proposal.localizedData),
661
- msg: "AI apply succeeded",
662
- proposal: getProposalLogSummary(proposal)
663
- });
664
- return Response.json({
665
- change,
666
- doc: {
667
- id: proposal.id
668
- },
669
- status: "applied"
670
- });
671
- }
672
- const preparedData = prepareProposalWriteData({
673
- collectionConfig,
674
- data: proposal.data,
675
- inferenceText,
676
- label: proposal.label,
677
- mode: proposal.action
678
- });
679
- if (preparedData.issues.length > 0 || !preparedData.data) {
680
- return failApply({
681
- debug: createApplyDebugPayload({
682
- details: {
683
- issues: preparedData.issues
684
- },
685
- phase: "apply_validation",
686
- proposal,
687
- reason: "invalid_collection_write_shape"
688
- }),
689
- error: "Proposal is invalid.",
690
- logMessage: "AI apply blocked: invalid collection proposal data",
691
- proposal
692
- });
693
- }
694
- if (proposal.action === "create") {
695
- const doc = await req.payload.create({
696
- collection: proposal.collection,
697
- data: preparedData.data,
698
- ...proposal.locale ? {
699
- locale: proposal.locale
700
- } : {},
701
- overrideAccess: false,
702
- req
703
- });
704
- const change = await logAIChange({
705
- changeLogCollection: options.changeLogCollection,
706
- context: logContext,
707
- proposal,
708
- req,
709
- target: {
710
- after: doc,
711
- before: {},
712
- documentID: doc.id
713
- }
714
- });
715
- logHandlerEvent(req, "info", {
716
- changeLogged: Boolean(change),
717
- documentID: doc.id,
718
- msg: "AI apply succeeded",
719
- proposal: getProposalLogSummary(proposal)
720
- });
721
- return Response.json({
722
- change,
723
- doc: getAppliedDocReference(doc),
724
- status: "applied"
725
- });
726
- }
727
- const before = await req.payload.findByID({
728
- collection: proposal.collection,
729
- depth: 2,
730
- id: proposal.id,
731
- ...proposal.locale ? {
732
- locale: proposal.locale
733
- } : {},
734
- req
735
- });
736
- const doc = await req.payload.update({
737
- collection: proposal.collection,
738
- data: preparedData.data,
739
- id: proposal.id,
740
- ...proposal.locale ? {
741
- locale: proposal.locale
742
- } : {},
743
- overrideAccess: false,
744
- req
745
- });
746
- const change = await logAIChange({
747
- changeLogCollection: options.changeLogCollection,
748
- context: logContext,
17
+ return await applyActionProposal({
18
+ failApply,
19
+ logContext,
20
+ options,
749
21
  proposal,
750
- req,
751
- target: {
752
- after: mergeData(before, preparedData.data),
753
- before,
754
- documentID: proposal.id
755
- }
756
- });
757
- logHandlerEvent(req, "info", {
758
- changeLogged: Boolean(change),
759
- documentID: proposal.id,
760
- msg: "AI apply succeeded",
761
- proposal: getProposalLogSummary(proposal)
762
- });
763
- return Response.json({
764
- change,
765
- doc: getAppliedDocReference(doc),
766
- status: "applied"
22
+ req
767
23
  });
768
24
  } catch (err) {
769
25
  const debug = createApplyDebugPayload({