@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,4 +1,4 @@
1
- import { isRecord } from "../payload/shared.js";
1
+ import { isRecord } from "../utils/data.js";
2
2
  const sensitiveKeyPatterns = [
3
3
  /^apiKey$/i,
4
4
  /^api_key$/i,
@@ -14,7 +14,7 @@ export type TokenUsageData = {
14
14
  outputTokens?: number;
15
15
  totalTokens?: number;
16
16
  };
17
- type TokenUsageLimit = {
17
+ export type TokenUsageLimit = {
18
18
  limit: number;
19
19
  period: "day" | "week";
20
20
  used: number;
@@ -35,4 +35,3 @@ export declare const recordTokenUsage: ({ model, now, provider, req, usage, user
35
35
  usage: TokenUsageData;
36
36
  userID: number | string;
37
37
  }) => Promise<void>;
38
- export {};
@@ -0,0 +1,42 @@
1
+ import type { PayloadHandler } from "payload";
2
+ import type { ActionProposal } from "../../features/proposals/types.js";
3
+ import type { ApplyActionLogContext } from "./types.js";
4
+ type AppliedDoc = {
5
+ id?: unknown;
6
+ };
7
+ type ChangeLogTarget = {
8
+ after: unknown;
9
+ before: unknown;
10
+ documentID?: unknown;
11
+ };
12
+ export declare const getAppliedDocReference: (doc: AppliedDoc | null | undefined) => {
13
+ id: {} | null;
14
+ } | undefined;
15
+ export declare const logAIChange: ({ changeLogCollection, context, target, proposal, req, }: {
16
+ changeLogCollection?: string;
17
+ context?: ApplyActionLogContext;
18
+ proposal: ActionProposal;
19
+ req: Parameters<PayloadHandler>[0];
20
+ target: ChangeLogTarget;
21
+ }) => Promise<{
22
+ action: "create" | "delete" | "update" | "updateGlobal";
23
+ additions: number;
24
+ after: unknown;
25
+ aiResponse: string | null;
26
+ before: unknown;
27
+ collection: string | null | undefined;
28
+ createdAt: string;
29
+ documentID: string | null;
30
+ inputTokens: number | null;
31
+ outputTokens: number | null;
32
+ prompt: string | null;
33
+ removals: number;
34
+ slug: string | null | undefined;
35
+ totalTokens: number | null;
36
+ targetType: string;
37
+ title: string;
38
+ userID: string | null;
39
+ userLabel: string | null;
40
+ url: string | null;
41
+ } | null>;
42
+ export {};
@@ -0,0 +1,150 @@
1
+ import { redactSensitiveData } from "../../features/sensitiveData.js";
2
+ import { getJSONLineKey } from "../../utils/data.js";
3
+ import { getProposalMeta } from "./validation.js";
4
+ export const getAppliedDocReference = (doc)=>{
5
+ return doc?.id === undefined ? undefined : {
6
+ id: doc.id
7
+ };
8
+ };
9
+ const countDiffChanges = ({ after, before })=>{
10
+ const beforeLines = JSON.stringify(before, null, 2).split("\n");
11
+ const afterLines = JSON.stringify(after, null, 2).split("\n");
12
+ const dp = Array.from({
13
+ length: beforeLines.length + 1
14
+ }, ()=>Array(afterLines.length + 1).fill(0));
15
+ let additions = 0;
16
+ let removals = 0;
17
+ for(let i = beforeLines.length - 1; i >= 0; i -= 1){
18
+ for(let j = afterLines.length - 1; j >= 0; j -= 1){
19
+ dp[i][j] = beforeLines[i] === afterLines[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
20
+ }
21
+ }
22
+ let beforeIndex = 0;
23
+ let afterIndex = 0;
24
+ while(beforeIndex < beforeLines.length && afterIndex < afterLines.length){
25
+ if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
26
+ beforeIndex += 1;
27
+ afterIndex += 1;
28
+ continue;
29
+ }
30
+ const beforeKey = getJSONLineKey(beforeLines[beforeIndex]);
31
+ const afterKey = getJSONLineKey(afterLines[afterIndex]);
32
+ if (beforeKey && beforeKey === afterKey) {
33
+ removals += 1;
34
+ additions += 1;
35
+ beforeIndex += 1;
36
+ afterIndex += 1;
37
+ continue;
38
+ }
39
+ if (dp[beforeIndex + 1][afterIndex] >= dp[beforeIndex][afterIndex + 1]) {
40
+ removals += 1;
41
+ beforeIndex += 1;
42
+ } else {
43
+ additions += 1;
44
+ afterIndex += 1;
45
+ }
46
+ }
47
+ removals += beforeLines.length - beforeIndex;
48
+ additions += afterLines.length - afterIndex;
49
+ return {
50
+ additions,
51
+ removals
52
+ };
53
+ };
54
+ const getTargetURL = ({ documentID, proposal, req })=>{
55
+ const adminRoute = req.payload.config.routes.admin || "/admin";
56
+ if (proposal.action === "updateGlobal") return `${adminRoute}/globals/${proposal.slug}`;
57
+ if (proposal.action === "delete") return null;
58
+ const id = proposal.action === "create" ? documentID : proposal.id;
59
+ if (!id) return null;
60
+ return `${adminRoute}/collections/${proposal.collection}/${id}`;
61
+ };
62
+ const getUserID = (req)=>{
63
+ const user = req.user;
64
+ return user?.id === undefined ? undefined : String(user.id);
65
+ };
66
+ const getUserLabel = (req)=>{
67
+ const user = req.user;
68
+ if (typeof user?.email === "string" && user.email) return user.email;
69
+ if (typeof user?.name === "string" && user.name) return user.name;
70
+ if (typeof user?.username === "string" && user.username) return user.username;
71
+ if (user?.id !== undefined) return String(user.id);
72
+ return null;
73
+ };
74
+ export const logAIChange = async ({ changeLogCollection, context, target, proposal, req })=>{
75
+ if (!changeLogCollection) return null;
76
+ const before = redactSensitiveData(target.before);
77
+ const after = redactSensitiveData(target.after);
78
+ const { additions, removals } = countDiffChanges({
79
+ after,
80
+ before
81
+ });
82
+ const proposalForLog = {
83
+ ...proposal
84
+ };
85
+ delete proposalForLog._aiSignature;
86
+ try {
87
+ await req.payload.create({
88
+ collection: changeLogCollection,
89
+ data: {
90
+ action: proposal.action,
91
+ additions,
92
+ after,
93
+ aiResponse: context?.aiResponse,
94
+ before,
95
+ collection: "collection" in proposal ? proposal.collection : undefined,
96
+ documentID: target.documentID === undefined ? undefined : String(target.documentID),
97
+ inputTokens: context?.tokenUsage?.inputTokens,
98
+ outputTokens: context?.tokenUsage?.outputTokens,
99
+ prompt: context?.prompt,
100
+ proposal: redactSensitiveData(proposalForLog),
101
+ removals,
102
+ slug: "slug" in proposal ? proposal.slug : undefined,
103
+ totalTokens: context?.tokenUsage?.totalTokens,
104
+ targetType: proposal.action === "updateGlobal" ? "global" : "collection",
105
+ targetURL: getTargetURL({
106
+ documentID: target.documentID,
107
+ proposal,
108
+ req
109
+ }),
110
+ title: proposal.label,
111
+ userID: getUserID(req),
112
+ userLabel: getUserLabel(req)
113
+ },
114
+ overrideAccess: true,
115
+ req
116
+ });
117
+ return {
118
+ action: proposal.action,
119
+ additions,
120
+ after,
121
+ aiResponse: context?.aiResponse || null,
122
+ before,
123
+ collection: "collection" in proposal ? proposal.collection : null,
124
+ createdAt: new Date().toISOString(),
125
+ documentID: target.documentID === undefined ? null : String(target.documentID),
126
+ inputTokens: context?.tokenUsage?.inputTokens ?? null,
127
+ outputTokens: context?.tokenUsage?.outputTokens ?? null,
128
+ prompt: context?.prompt || null,
129
+ removals,
130
+ slug: "slug" in proposal ? proposal.slug : null,
131
+ totalTokens: context?.tokenUsage?.totalTokens ?? null,
132
+ targetType: proposal.action === "updateGlobal" ? "global" : "collection",
133
+ title: proposal.label,
134
+ userID: getUserID(req) || null,
135
+ userLabel: getUserLabel(req),
136
+ url: getTargetURL({
137
+ documentID: target.documentID,
138
+ proposal,
139
+ req
140
+ })
141
+ };
142
+ } catch (err) {
143
+ req.payload.logger.error({
144
+ err,
145
+ msg: "AI change log entry could not be written",
146
+ proposal: getProposalMeta(proposal)
147
+ });
148
+ return null;
149
+ }
150
+ };
@@ -0,0 +1,18 @@
1
+ import type { PayloadHandler } from "payload";
2
+ import type { ActionProposal } from "../../features/proposals/types.js";
3
+ import type { ApplyActionLogContext, ApplyActionOptions, ApplyDebugPayload } from "./types.js";
4
+ type FailApply = (failure: {
5
+ debug: ApplyDebugPayload;
6
+ error: string;
7
+ logMessage: string;
8
+ proposal?: Partial<ActionProposal>;
9
+ status?: number;
10
+ }) => Response;
11
+ export declare const applyActionProposal: ({ failApply, logContext, options, proposal, req, }: {
12
+ failApply: FailApply;
13
+ logContext: ApplyActionLogContext;
14
+ options: ApplyActionOptions;
15
+ proposal: ActionProposal;
16
+ req: Parameters<PayloadHandler>[0];
17
+ }) => Promise<Response>;
18
+ export {};
@@ -0,0 +1,377 @@
1
+ import { prepareLocalizedTargetUpdates, prepareProposalTargetData, resolveProposalTarget } from "../../features/proposals/target.js";
2
+ import { applyLocalizedRequiredFallbackToPreparedData } from "../../features/proposals/data.js";
3
+ import { isCollectionActionAllowed } from "../../features/collectionPermissions.js";
4
+ import { getDefaultLocale, hasLocalizedData, mergeData } from "../../utils/data.js";
5
+ import { logHandlerEvent } from "../../utils/logging.js";
6
+ import { getAppliedDocReference, logAIChange } from "./audit.js";
7
+ import { createApplyDebugPayload, getProposalLogSummary } from "./validation.js";
8
+ const isAllowedCollection = (req, collection, collections, action = "read")=>isCollectionActionAllowed({
9
+ action,
10
+ permissions: collections,
11
+ req,
12
+ slug: collection
13
+ });
14
+ export const applyActionProposal = async ({ failApply, logContext, options, proposal, req })=>{
15
+ const inferenceText = logContext.prompt;
16
+ const defaultLocale = getDefaultLocale(req);
17
+ const target = resolveProposalTarget({
18
+ proposal,
19
+ req
20
+ });
21
+ if (proposal.action === "updateGlobal") {
22
+ if (!target || target.type !== "global") {
23
+ return failApply({
24
+ debug: createApplyDebugPayload({
25
+ phase: "apply_validation",
26
+ proposal,
27
+ reason: "unknown_global"
28
+ }),
29
+ error: "Unknown global",
30
+ logMessage: "AI apply blocked: unknown global",
31
+ proposal
32
+ });
33
+ }
34
+ if (hasLocalizedData(proposal)) {
35
+ const preparedData = prepareProposalTargetData({
36
+ inferenceText,
37
+ proposal,
38
+ target
39
+ });
40
+ if (preparedData.issues.length > 0 || !preparedData.localizedData) {
41
+ return failApply({
42
+ debug: createApplyDebugPayload({
43
+ details: {
44
+ issues: preparedData.issues
45
+ },
46
+ phase: "apply_validation",
47
+ proposal,
48
+ reason: "invalid_global_write_shape"
49
+ }),
50
+ error: "Proposal is invalid.",
51
+ logMessage: "AI apply blocked: invalid global proposal data",
52
+ proposal
53
+ });
54
+ }
55
+ const localizedResults = await prepareLocalizedTargetUpdates({
56
+ defaultLocale: defaultLocale || undefined,
57
+ localizedData: preparedData.localizedData,
58
+ target
59
+ });
60
+ await Promise.all(localizedResults.map(({ data, locale })=>target.update(data, locale)));
61
+ const change = await logAIChange({
62
+ changeLogCollection: options.changeLogCollection,
63
+ context: logContext,
64
+ proposal,
65
+ req,
66
+ target: {
67
+ after: Object.fromEntries(localizedResults.map(({ after, locale })=>[
68
+ locale,
69
+ after
70
+ ])),
71
+ before: Object.fromEntries(localizedResults.map(({ before, locale })=>[
72
+ locale,
73
+ before
74
+ ]))
75
+ }
76
+ });
77
+ logHandlerEvent(req, "info", {
78
+ changeLogged: Boolean(change),
79
+ locales: Object.keys(proposal.localizedData),
80
+ msg: "AI apply succeeded",
81
+ proposal: getProposalLogSummary(proposal)
82
+ });
83
+ return Response.json({
84
+ change,
85
+ doc: undefined,
86
+ status: "applied"
87
+ });
88
+ }
89
+ const preparedData = prepareProposalTargetData({
90
+ inferenceText,
91
+ proposal,
92
+ target
93
+ });
94
+ if (preparedData.issues.length > 0 || !preparedData.data) {
95
+ return failApply({
96
+ debug: createApplyDebugPayload({
97
+ details: {
98
+ issues: preparedData.issues
99
+ },
100
+ phase: "apply_validation",
101
+ proposal,
102
+ reason: "invalid_global_write_shape"
103
+ }),
104
+ error: "Proposal is invalid.",
105
+ logMessage: "AI apply blocked: invalid global proposal data",
106
+ proposal
107
+ });
108
+ }
109
+ const before = await target.read({
110
+ locale: proposal.locale
111
+ });
112
+ const doc = await target.update(preparedData.data, proposal.locale);
113
+ const change = await logAIChange({
114
+ changeLogCollection: options.changeLogCollection,
115
+ context: logContext,
116
+ proposal,
117
+ req,
118
+ target: {
119
+ after: mergeData(before, preparedData.data),
120
+ before
121
+ }
122
+ });
123
+ logHandlerEvent(req, "info", {
124
+ changeLogged: Boolean(change),
125
+ locale: proposal.locale,
126
+ msg: "AI apply succeeded",
127
+ proposal: getProposalLogSummary(proposal)
128
+ });
129
+ return Response.json({
130
+ change,
131
+ doc: getAppliedDocReference(doc),
132
+ status: "applied"
133
+ });
134
+ }
135
+ if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) {
136
+ return failApply({
137
+ debug: createApplyDebugPayload({
138
+ phase: "authorization",
139
+ proposal,
140
+ reason: "unknown_or_disallowed_collection"
141
+ }),
142
+ error: "Unknown collection",
143
+ logMessage: "AI apply blocked: unknown or disallowed collection",
144
+ proposal
145
+ });
146
+ }
147
+ if (!target || target.type !== "collection") {
148
+ return failApply({
149
+ debug: createApplyDebugPayload({
150
+ phase: "authorization",
151
+ proposal,
152
+ reason: "unknown_or_disallowed_collection"
153
+ }),
154
+ error: "Unknown collection",
155
+ logMessage: "AI apply blocked: unknown or disallowed collection",
156
+ proposal
157
+ });
158
+ }
159
+ if (proposal.action === "delete") {
160
+ const doc = await target.delete();
161
+ const change = await logAIChange({
162
+ changeLogCollection: options.changeLogCollection,
163
+ context: logContext,
164
+ proposal,
165
+ req,
166
+ target: {
167
+ after: {},
168
+ before: doc,
169
+ documentID: proposal.id
170
+ }
171
+ });
172
+ logHandlerEvent(req, "info", {
173
+ changeLogged: Boolean(change),
174
+ documentID: proposal.id,
175
+ msg: "AI apply succeeded",
176
+ proposal: getProposalLogSummary(proposal)
177
+ });
178
+ return Response.json({
179
+ change,
180
+ doc: getAppliedDocReference(doc),
181
+ status: "applied"
182
+ });
183
+ }
184
+ if (hasLocalizedData(proposal)) {
185
+ const preparedData = prepareProposalTargetData({
186
+ inferenceText,
187
+ proposal,
188
+ target
189
+ });
190
+ if (preparedData.issues.length > 0 || !preparedData.localizedData) {
191
+ return failApply({
192
+ debug: createApplyDebugPayload({
193
+ details: {
194
+ issues: preparedData.issues
195
+ },
196
+ phase: "apply_validation",
197
+ proposal,
198
+ reason: "invalid_collection_write_shape"
199
+ }),
200
+ error: "Proposal is invalid.",
201
+ logMessage: "AI apply blocked: invalid collection proposal data",
202
+ proposal
203
+ });
204
+ }
205
+ if (proposal.action === "create") {
206
+ const localeEntries = Object.entries(preparedData.localizedData);
207
+ const [firstLocale, firstLocaleData] = localeEntries[0] || [];
208
+ if (!firstLocale) {
209
+ return failApply({
210
+ debug: createApplyDebugPayload({
211
+ phase: "apply_validation",
212
+ proposal,
213
+ reason: "localized_create_without_locales"
214
+ }),
215
+ error: "Proposal is invalid.",
216
+ logMessage: "AI apply blocked: localized create proposal has no locales",
217
+ proposal
218
+ });
219
+ }
220
+ const doc = await target.create(firstLocaleData, firstLocale);
221
+ const beforeByLocale = {
222
+ [firstLocale]: {}
223
+ };
224
+ const afterByLocale = {
225
+ [firstLocale]: doc
226
+ };
227
+ const fallbackSource = doc;
228
+ const localizedResults = await Promise.all(localeEntries.slice(1).map(async ([locale, localeData])=>{
229
+ const completedData = applyLocalizedRequiredFallbackToPreparedData({
230
+ fallbackSource,
231
+ fields: target.fields,
232
+ preparedData: localeData
233
+ });
234
+ await target.update(completedData, locale, String(doc.id));
235
+ return {
236
+ completedData,
237
+ locale
238
+ };
239
+ }));
240
+ localizedResults.forEach(({ completedData, locale })=>{
241
+ beforeByLocale[locale] = {};
242
+ afterByLocale[locale] = completedData;
243
+ });
244
+ const change = await logAIChange({
245
+ changeLogCollection: options.changeLogCollection,
246
+ context: logContext,
247
+ proposal,
248
+ req,
249
+ target: {
250
+ after: afterByLocale,
251
+ before: beforeByLocale,
252
+ documentID: doc.id
253
+ }
254
+ });
255
+ logHandlerEvent(req, "info", {
256
+ changeLogged: Boolean(change),
257
+ documentID: doc.id,
258
+ locales: localeEntries.map(([locale])=>locale),
259
+ msg: "AI apply succeeded",
260
+ proposal: getProposalLogSummary(proposal)
261
+ });
262
+ return Response.json({
263
+ change,
264
+ doc: getAppliedDocReference(doc),
265
+ status: "applied"
266
+ });
267
+ }
268
+ const localizedResults = await prepareLocalizedTargetUpdates({
269
+ defaultLocale: defaultLocale || undefined,
270
+ localizedData: preparedData.localizedData,
271
+ target
272
+ });
273
+ await Promise.all(localizedResults.map(({ data, locale })=>target.update(data, locale)));
274
+ const change = await logAIChange({
275
+ changeLogCollection: options.changeLogCollection,
276
+ context: logContext,
277
+ proposal,
278
+ req,
279
+ target: {
280
+ after: Object.fromEntries(localizedResults.map(({ after, locale })=>[
281
+ locale,
282
+ after
283
+ ])),
284
+ before: Object.fromEntries(localizedResults.map(({ before, locale })=>[
285
+ locale,
286
+ before
287
+ ])),
288
+ documentID: proposal.id
289
+ }
290
+ });
291
+ logHandlerEvent(req, "info", {
292
+ changeLogged: Boolean(change),
293
+ documentID: proposal.id,
294
+ locales: Object.keys(proposal.localizedData),
295
+ msg: "AI apply succeeded",
296
+ proposal: getProposalLogSummary(proposal)
297
+ });
298
+ return Response.json({
299
+ change,
300
+ doc: {
301
+ id: proposal.id
302
+ },
303
+ status: "applied"
304
+ });
305
+ }
306
+ const preparedData = prepareProposalTargetData({
307
+ inferenceText,
308
+ proposal,
309
+ target
310
+ });
311
+ if (preparedData.issues.length > 0 || !preparedData.data) {
312
+ return failApply({
313
+ debug: createApplyDebugPayload({
314
+ details: {
315
+ issues: preparedData.issues
316
+ },
317
+ phase: "apply_validation",
318
+ proposal,
319
+ reason: "invalid_collection_write_shape"
320
+ }),
321
+ error: "Proposal is invalid.",
322
+ logMessage: "AI apply blocked: invalid collection proposal data",
323
+ proposal
324
+ });
325
+ }
326
+ if (proposal.action === "create") {
327
+ const doc = await target.create(preparedData.data, proposal.locale);
328
+ const change = await logAIChange({
329
+ changeLogCollection: options.changeLogCollection,
330
+ context: logContext,
331
+ proposal,
332
+ req,
333
+ target: {
334
+ after: doc,
335
+ before: {},
336
+ documentID: doc.id
337
+ }
338
+ });
339
+ logHandlerEvent(req, "info", {
340
+ changeLogged: Boolean(change),
341
+ documentID: doc.id,
342
+ msg: "AI apply succeeded",
343
+ proposal: getProposalLogSummary(proposal)
344
+ });
345
+ return Response.json({
346
+ change,
347
+ doc: getAppliedDocReference(doc),
348
+ status: "applied"
349
+ });
350
+ }
351
+ const before = await target.read({
352
+ locale: proposal.locale
353
+ });
354
+ const doc = await target.update(preparedData.data, proposal.locale);
355
+ const change = await logAIChange({
356
+ changeLogCollection: options.changeLogCollection,
357
+ context: logContext,
358
+ proposal,
359
+ req,
360
+ target: {
361
+ after: mergeData(before, preparedData.data),
362
+ before,
363
+ documentID: proposal.id
364
+ }
365
+ });
366
+ logHandlerEvent(req, "info", {
367
+ changeLogged: Boolean(change),
368
+ documentID: proposal.id,
369
+ msg: "AI apply succeeded",
370
+ proposal: getProposalLogSummary(proposal)
371
+ });
372
+ return Response.json({
373
+ change,
374
+ doc: getAppliedDocReference(doc),
375
+ status: "applied"
376
+ });
377
+ };
@@ -0,0 +1,33 @@
1
+ import type { ResolvedCollectionPermissionMap } from "../../features/collectionPermissions.js";
2
+ export type ApplyActionBody = {
3
+ aiResponse?: string;
4
+ tokenUsage?: {
5
+ inputTokens?: unknown;
6
+ outputTokens?: unknown;
7
+ totalTokens?: unknown;
8
+ };
9
+ prompt?: string;
10
+ proposal?: unknown;
11
+ };
12
+ export type ApplyActionOptions = {
13
+ changeLogCollection?: string;
14
+ collections?: ResolvedCollectionPermissionMap;
15
+ };
16
+ export type ApplyActionLogContext = {
17
+ aiResponse?: string;
18
+ prompt?: string;
19
+ tokenUsage?: {
20
+ inputTokens?: number;
21
+ outputTokens?: number;
22
+ totalTokens?: number;
23
+ };
24
+ };
25
+ export type ApplyDebugPayload = {
26
+ action?: unknown;
27
+ collection?: unknown;
28
+ details?: Record<string, unknown>;
29
+ id?: unknown;
30
+ phase: "apply_validation" | "authorization" | "payload_operation";
31
+ reason: string;
32
+ slug?: unknown;
33
+ };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,45 @@
1
+ import type { PayloadHandler } from "payload";
2
+ import type { ActionProposal } from "../../features/proposals/types.js";
3
+ import type { ApplyActionLogContext, ApplyDebugPayload } from "./types.js";
4
+ type ProposalMeta = {
5
+ action?: unknown;
6
+ collection?: unknown;
7
+ id?: unknown;
8
+ slug?: unknown;
9
+ };
10
+ type ApplyFailure = {
11
+ debug: ApplyDebugPayload;
12
+ error: string;
13
+ logMessage: string;
14
+ proposal?: Partial<ActionProposal>;
15
+ status?: number;
16
+ };
17
+ type ApplyRequestResolution = {
18
+ failure: ApplyFailure;
19
+ ok: false;
20
+ } | {
21
+ context: ApplyActionLogContext;
22
+ ok: true;
23
+ proposal: ActionProposal;
24
+ };
25
+ export declare const getProposalLogSummary: (proposal: ActionProposal) => {
26
+ action: "create" | "delete" | "update" | "updateGlobal";
27
+ collection: string | undefined;
28
+ hasData: boolean;
29
+ hasLocalizedData: boolean;
30
+ id: string | undefined;
31
+ label: string;
32
+ locale: string | undefined;
33
+ locales: string[] | undefined;
34
+ slug: string | undefined;
35
+ };
36
+ export declare const getProposalMeta: (proposal?: Partial<ActionProposal>) => ProposalMeta;
37
+ export declare const createApplyDebugPayload: ({ details, phase, proposal, reason, }: {
38
+ details?: Record<string, unknown>;
39
+ phase: ApplyDebugPayload["phase"];
40
+ proposal?: Partial<ActionProposal>;
41
+ reason: string;
42
+ }) => ApplyDebugPayload;
43
+ export declare const resolveApplyRequest: (req: Parameters<PayloadHandler>[0]) => Promise<ApplyRequestResolution>;
44
+ export declare const createApplyFailureResponse: (req: Parameters<PayloadHandler>[0], applyFailure: ApplyFailure) => Response;
45
+ export {};