@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.
- package/dist/components/action-toast/ActionToast.d.ts +1 -14
- package/dist/components/action-toast/ActionToast.js +1 -1
- package/dist/components/ai-input/AIInput.js +1 -1
- package/dist/components/audit-log-list/AuditLogList.d.ts +2 -3
- package/dist/components/diff-dialog/DiffDialog.d.ts +2 -6
- package/dist/components/diff-dialog/DiffDialog.js +1 -1
- package/dist/components/generate-field/GenerateField.js +2 -0
- package/dist/components/hooks/useAIChatStream.d.ts +1 -1
- package/dist/components/hooks/useAISettings.d.ts +1 -1
- package/dist/components/hooks/useAISettings.js +1 -1
- package/dist/components/hooks/useDocumentMentionSuggestions.js +1 -1
- package/dist/components/hooks/useMentions.js +1 -1
- package/dist/components/hooks/usePluginConfig.d.ts +6 -6
- package/dist/components/hooks/usePluginConfig.js +1 -1
- package/dist/{payload → features}/collectionPermissions.js +1 -1
- package/dist/{payload/textFieldGeneration.d.ts → features/content/fieldGeneration.d.ts} +4 -0
- package/dist/{payload/textFieldGeneration.js → features/content/fieldGeneration.js} +19 -3
- package/dist/{ai → features}/promptCaching.d.ts +2 -2
- package/dist/features/proposals/containerNormalization.d.ts +8 -0
- package/dist/features/proposals/containerNormalization.js +112 -0
- package/dist/{payload/proposalData.d.ts → features/proposals/data.d.ts} +5 -10
- package/dist/features/proposals/data.js +113 -0
- package/dist/features/proposals/fieldNormalization.d.ts +2 -0
- package/dist/features/proposals/fieldNormalization.js +69 -0
- package/dist/features/proposals/normalizationTypes.d.ts +31 -0
- package/dist/features/proposals/normalizationTypes.js +1 -0
- package/dist/features/proposals/optionInference.d.ts +13 -0
- package/dist/features/proposals/optionInference.js +46 -0
- package/dist/features/proposals/recordNormalization.d.ts +6 -0
- package/dist/features/proposals/recordNormalization.js +78 -0
- package/dist/features/proposals/relationshipNormalization.d.ts +8 -0
- package/dist/features/proposals/relationshipNormalization.js +71 -0
- package/dist/{ai/proposalRepair.d.ts → features/proposals/repair.d.ts} +1 -1
- package/dist/{ai/proposalSigning.d.ts → features/proposals/signing.d.ts} +2 -5
- package/dist/{ai/proposalSigning.js → features/proposals/signing.js} +1 -1
- package/dist/features/proposals/target.d.ts +52 -0
- package/dist/features/proposals/target.js +125 -0
- package/dist/features/proposals/types.d.ts +49 -0
- package/dist/features/proposals/types.js +1 -0
- package/dist/features/proposals/validation.d.ts +12 -0
- package/dist/features/proposals/validation.js +26 -0
- package/dist/features/providers/requestContext.d.ts +53 -0
- package/dist/features/providers/requestContext.js +90 -0
- package/dist/{ai/providerRuntime.d.ts → features/providers/runtime.d.ts} +1 -1
- package/dist/{ai/providerRuntime.js → features/providers/runtime.js} +1 -1
- package/dist/features/schema/context.d.ts +7 -0
- package/dist/features/schema/context.js +11 -0
- package/dist/{payload/schemaContext.d.ts → features/schema/description.d.ts} +6 -33
- package/dist/{payload/schemaContext.js → features/schema/description.js} +3 -207
- package/dist/{payload/toolFieldSelection.d.ts → features/schema/fieldSelection.d.ts} +1 -1
- package/dist/{payload/toolFieldSelection.js → features/schema/fieldSelection.js} +2 -2
- package/dist/features/schema/mentionDocuments.d.ts +15 -0
- package/dist/features/schema/mentionDocuments.js +50 -0
- package/dist/features/schema/mentions.d.ts +18 -0
- package/dist/features/schema/mentions.js +100 -0
- package/dist/{payload/normalizeData.js → features/schema/normalize.js} +1 -1
- package/dist/features/schema/registry.d.ts +30 -0
- package/dist/features/schema/registry.js +73 -0
- package/dist/{payload → features/schema}/toolSchemas.d.ts +1 -1
- package/dist/{payload → features/schema}/toolSchemas.js +1 -1
- package/dist/{ai → features}/sensitiveData.js +1 -1
- package/dist/{ai → features}/tokenUsage.d.ts +1 -2
- package/dist/handlers/apply/audit.d.ts +42 -0
- package/dist/handlers/apply/audit.js +150 -0
- package/dist/handlers/apply/operations.d.ts +18 -0
- package/dist/handlers/apply/operations.js +377 -0
- package/dist/handlers/apply/types.d.ts +33 -0
- package/dist/handlers/apply/types.js +1 -0
- package/dist/handlers/apply/validation.d.ts +45 -0
- package/dist/handlers/apply/validation.js +125 -0
- package/dist/handlers/applyActionHandler.d.ts +1 -6
- package/dist/handlers/applyActionHandler.js +13 -757
- package/dist/handlers/auditLogHandler.js +4 -8
- package/dist/handlers/chat/intent.d.ts +32 -0
- package/dist/handlers/chat/intent.js +155 -0
- package/dist/handlers/chat/prompt.d.ts +20 -0
- package/dist/handlers/chat/prompt.js +55 -0
- package/dist/handlers/chat/proposalTools.d.ts +96 -0
- package/dist/handlers/chat/proposalTools.js +451 -0
- package/dist/handlers/chat/streaming.d.ts +38 -0
- package/dist/handlers/chat/streaming.js +136 -0
- package/dist/handlers/chat/toolFactory.d.ts +79 -0
- package/dist/handlers/chat/toolFactory.js +156 -0
- package/dist/handlers/chat/types.d.ts +57 -0
- package/dist/handlers/chat/types.js +1 -0
- package/dist/handlers/chatHandler.d.ts +1 -44
- package/dist/handlers/chatHandler.js +186 -1162
- package/dist/handlers/generateFieldHandler.d.ts +3 -8
- package/dist/handlers/generateFieldHandler.js +71 -99
- package/dist/handlers/http.d.ts +17 -0
- package/dist/handlers/http.js +21 -0
- package/dist/handlers/mediaUploadHandler.js +12 -34
- package/dist/handlers/mentionSuggestionHandler.d.ts +1 -1
- package/dist/handlers/mentionSuggestionHandler.js +6 -10
- package/dist/handlers/proposalDiffHandler.d.ts +1 -1
- package/dist/handlers/proposalDiffHandler.js +72 -195
- package/dist/handlers/translateDocumentHandler.d.ts +3 -8
- package/dist/handlers/translateDocumentHandler.js +26 -82
- package/dist/index.d.ts +4 -30
- package/dist/index.js +31 -421
- package/dist/plugin/admin.d.ts +30 -0
- package/dist/plugin/admin.js +155 -0
- package/dist/plugin/collections.d.ts +3 -0
- package/dist/plugin/collections.js +169 -0
- package/dist/plugin/endpoints.d.ts +23 -0
- package/dist/plugin/endpoints.js +84 -0
- package/dist/plugin/options.d.ts +4 -0
- package/dist/plugin/options.js +13 -0
- package/dist/plugin/types.d.ts +28 -0
- package/dist/plugin/types.js +1 -0
- package/dist/{payload/shared.d.ts → utils/data.d.ts} +2 -12
- package/package.json +1 -1
- package/dist/payload/proposalData.js +0 -578
- /package/dist/{payload → features}/collectionPermissions.d.ts +0 -0
- /package/dist/{payload → features/content}/documentTranslation.d.ts +0 -0
- /package/dist/{payload → features/content}/documentTranslation.js +0 -0
- /package/dist/{ai → features}/promptCaching.js +0 -0
- /package/dist/{ai/proposalRepair.js → features/proposals/repair.js} +0 -0
- /package/dist/{ai/providerOptions.d.ts → features/providers/options.d.ts} +0 -0
- /package/dist/{ai/providerOptions.js → features/providers/options.js} +0 -0
- /package/dist/{payload/normalizeData.d.ts → features/schema/normalize.d.ts} +0 -0
- /package/dist/{ai → features}/sensitiveData.d.ts +0 -0
- /package/dist/{ai → features}/tokenUsage.js +0 -0
- /package/dist/{payload/shared.js → utils/data.js} +0 -0
- /package/dist/{payload → utils}/logging.d.ts +0 -0
- /package/dist/{payload → utils}/logging.js +0 -0
|
@@ -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 {};
|