@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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FieldConfig } from "../schema/normalize.js";
|
|
2
|
+
export declare const getFieldOptionValues: (field: FieldConfig) => string[];
|
|
3
|
+
export declare const inferOptionValueFromText: ({ field, requireFieldMention, text, }: {
|
|
4
|
+
field: FieldConfig;
|
|
5
|
+
requireFieldMention?: boolean;
|
|
6
|
+
text?: string;
|
|
7
|
+
}) => string | undefined;
|
|
8
|
+
export declare const getDefaultFieldValue: ({ field, inferenceText, label, titleFieldName, }: {
|
|
9
|
+
field: FieldConfig;
|
|
10
|
+
inferenceText?: string;
|
|
11
|
+
label: string;
|
|
12
|
+
titleFieldName?: string;
|
|
13
|
+
}) => {} | null | undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getOptionValue, getSafeProposalLabel } from "../../utils/data.js";
|
|
2
|
+
const isTitleLikeField = (field, titleFieldName)=>{
|
|
3
|
+
const normalizedName = field.name?.toLowerCase();
|
|
4
|
+
return Boolean(field.name && (field.name === titleFieldName || normalizedName === "title" || normalizedName === "name" || normalizedName === "label" || normalizedName === "headline"));
|
|
5
|
+
};
|
|
6
|
+
export const getFieldOptionValues = (field)=>(field.options || []).map((option)=>getOptionValue(option)).filter((option)=>Boolean(option));
|
|
7
|
+
const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
|
+
const getFieldNameVariants = (fieldName)=>{
|
|
9
|
+
if (!fieldName) return [];
|
|
10
|
+
const spacedName = fieldName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
|
|
11
|
+
return Array.from(new Set([
|
|
12
|
+
fieldName.toLowerCase(),
|
|
13
|
+
spacedName
|
|
14
|
+
])).filter(Boolean);
|
|
15
|
+
};
|
|
16
|
+
export const inferOptionValueFromText = ({ field, requireFieldMention = false, text })=>{
|
|
17
|
+
const normalizedText = text?.trim().toLowerCase();
|
|
18
|
+
if (!normalizedText) return undefined;
|
|
19
|
+
if (requireFieldMention && !getFieldNameVariants(field.name).some((variant)=>new RegExp(`\\b${escapeRegExp(variant)}\\b`, "i").test(normalizedText))) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return getFieldOptionValues(field).sort((left, right)=>right.length - left.length).find((optionValue)=>new RegExp(`\\b${escapeRegExp(optionValue.toLowerCase())}\\b`, "i").test(normalizedText));
|
|
23
|
+
};
|
|
24
|
+
export const getDefaultFieldValue = ({ field, inferenceText, label, titleFieldName })=>{
|
|
25
|
+
if (field.name === "_status") return "draft";
|
|
26
|
+
if (field.type === "checkbox") return false;
|
|
27
|
+
if (field.type === "select" || field.type === "radio") {
|
|
28
|
+
const inferredOptionValue = inferOptionValueFromText({
|
|
29
|
+
field,
|
|
30
|
+
requireFieldMention: true,
|
|
31
|
+
text: inferenceText || label
|
|
32
|
+
});
|
|
33
|
+
if (inferredOptionValue !== undefined) return inferredOptionValue;
|
|
34
|
+
if (field.defaultValue !== undefined) return field.defaultValue;
|
|
35
|
+
return getOptionValue(field.options?.[0]);
|
|
36
|
+
}
|
|
37
|
+
if (field.defaultValue !== undefined) return field.defaultValue;
|
|
38
|
+
if ([
|
|
39
|
+
"email",
|
|
40
|
+
"text",
|
|
41
|
+
"textarea"
|
|
42
|
+
].includes(field.type || "") && isTitleLikeField(field, titleFieldName)) {
|
|
43
|
+
return getSafeProposalLabel(label);
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type FieldConfig } from "../schema/normalize.js";
|
|
2
|
+
import type { NormalizationState } from "./normalizationTypes.js";
|
|
3
|
+
export declare const prepareSingleLocaleData: ({ data, fields, ...state }: NormalizationState & {
|
|
4
|
+
data: Record<string, unknown>;
|
|
5
|
+
fields: readonly FieldConfig[];
|
|
6
|
+
}) => Record<string, unknown>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { normalizeDataForFields } from "../schema/normalize.js";
|
|
2
|
+
import { normalizeFieldValue } from "./fieldNormalization.js";
|
|
3
|
+
import { getDefaultFieldValue, inferOptionValueFromText } from "./optionInference.js";
|
|
4
|
+
const normalizeRecordForFields = ({ allowSafeFallback, coercedFields, enforceRequiredChildren, fields, inferenceText, issues, label, mode, path, titleFieldName, value })=>{
|
|
5
|
+
const normalizedData = {};
|
|
6
|
+
const fieldsByName = new Map(fields.filter((field)=>Boolean(field.name)).map((field)=>[
|
|
7
|
+
field.name,
|
|
8
|
+
field
|
|
9
|
+
]));
|
|
10
|
+
for (const [key, fieldValue] of Object.entries(value)){
|
|
11
|
+
const field = fieldsByName.get(key);
|
|
12
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
13
|
+
if (!field) {
|
|
14
|
+
issues.push({
|
|
15
|
+
code: "unknown_field",
|
|
16
|
+
message: "Field does not exist in the schema.",
|
|
17
|
+
path: fieldPath
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const normalizedValue = normalizeFieldValue({
|
|
22
|
+
coercedFields,
|
|
23
|
+
enforceRequiredChildren,
|
|
24
|
+
field,
|
|
25
|
+
inferenceText,
|
|
26
|
+
issues,
|
|
27
|
+
label,
|
|
28
|
+
mode,
|
|
29
|
+
normalizeRecord: normalizeRecordForFields,
|
|
30
|
+
path: fieldPath,
|
|
31
|
+
titleFieldName,
|
|
32
|
+
value: fieldValue
|
|
33
|
+
});
|
|
34
|
+
if (normalizedValue !== undefined) normalizedData[key] = normalizedValue;
|
|
35
|
+
}
|
|
36
|
+
if (mode === "create" || enforceRequiredChildren) {
|
|
37
|
+
for (const field of fields.filter((candidate)=>Boolean(candidate.name) && candidate.required === true)){
|
|
38
|
+
const fieldPath = path ? `${path}.${field.name}` : field.name;
|
|
39
|
+
if (normalizedData[field.name] !== undefined) continue;
|
|
40
|
+
const fallbackValue = allowSafeFallback ? getDefaultFieldValue({
|
|
41
|
+
field,
|
|
42
|
+
inferenceText,
|
|
43
|
+
label,
|
|
44
|
+
titleFieldName
|
|
45
|
+
}) : undefined;
|
|
46
|
+
if (fallbackValue !== undefined) {
|
|
47
|
+
normalizedData[field.name] = fallbackValue;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
issues.push({
|
|
51
|
+
code: "missing_required_field",
|
|
52
|
+
message: "Required field is missing.",
|
|
53
|
+
path: fieldPath
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const field of fields.filter((candidate)=>Boolean(candidate.name))){
|
|
58
|
+
if (normalizedData[field.name] !== undefined || field.type !== "select" && field.type !== "radio") continue;
|
|
59
|
+
const inferredOptionValue = inferOptionValueFromText({
|
|
60
|
+
field,
|
|
61
|
+
requireFieldMention: true,
|
|
62
|
+
text: inferenceText || label
|
|
63
|
+
});
|
|
64
|
+
if (inferredOptionValue !== undefined) {
|
|
65
|
+
normalizedData[field.name] = inferredOptionValue;
|
|
66
|
+
coercedFields.push(path ? `${path}.${field.name}` : field.name);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return normalizedData;
|
|
70
|
+
};
|
|
71
|
+
export const prepareSingleLocaleData = ({ data, fields, ...state })=>normalizeDataForFields(fields, normalizeRecordForFields({
|
|
72
|
+
...state,
|
|
73
|
+
allowSafeFallback: true,
|
|
74
|
+
enforceRequiredChildren: false,
|
|
75
|
+
fields,
|
|
76
|
+
path: "",
|
|
77
|
+
value: data
|
|
78
|
+
})).data;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FieldConfig } from "../schema/normalize.js";
|
|
2
|
+
import type { ProposalValidationIssue } from "./normalizationTypes.js";
|
|
3
|
+
export declare const normalizeRelationshipValue: ({ field, issues, path, value, }: {
|
|
4
|
+
field: FieldConfig;
|
|
5
|
+
issues: ProposalValidationIssue[];
|
|
6
|
+
path: string;
|
|
7
|
+
value: unknown;
|
|
8
|
+
}) => unknown;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isRecord } from "../../utils/data.js";
|
|
2
|
+
const normalizeRelationshipScalar = (value)=>{
|
|
3
|
+
if (typeof value === "number") {
|
|
4
|
+
if (!Number.isInteger(value) || value <= 0) return undefined;
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
const trimmedValue = value.trim();
|
|
8
|
+
if (!trimmedValue || /\s/.test(trimmedValue)) return undefined;
|
|
9
|
+
if (/^\d+$/.test(trimmedValue)) {
|
|
10
|
+
const numericValue = Number(trimmedValue);
|
|
11
|
+
return numericValue > 0 ? numericValue : undefined;
|
|
12
|
+
}
|
|
13
|
+
return trimmedValue;
|
|
14
|
+
};
|
|
15
|
+
export const normalizeRelationshipValue = ({ field, issues, path, value })=>{
|
|
16
|
+
const allowedRelationTargets = Array.isArray(field.relationTo) ? field.relationTo.filter((item)=>typeof item === "string") : typeof field.relationTo === "string" ? [
|
|
17
|
+
field.relationTo
|
|
18
|
+
] : [];
|
|
19
|
+
const addInvalidRelationship = (message, itemPath)=>{
|
|
20
|
+
issues.push({
|
|
21
|
+
code: "invalid_relationship",
|
|
22
|
+
message,
|
|
23
|
+
path: itemPath
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const normalizeSingle = (item, itemPath)=>{
|
|
27
|
+
if (typeof item === "number" || typeof item === "string") {
|
|
28
|
+
const normalizedValue = normalizeRelationshipScalar(item);
|
|
29
|
+
if (normalizedValue !== undefined) return normalizedValue;
|
|
30
|
+
addInvalidRelationship(typeof item === "string" ? "Relationship and upload fields must use a valid document ID, not free text." : "Relationship and upload fields must use a valid document ID.", itemPath);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (!isRecord(item)) {
|
|
34
|
+
addInvalidRelationship("Relationship and upload fields must use a document ID or an object with id/value.", itemPath);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (typeof item.id === "string" || typeof item.id === "number") {
|
|
38
|
+
const normalizedValue = normalizeRelationshipScalar(item.id);
|
|
39
|
+
if (normalizedValue !== undefined) return normalizedValue;
|
|
40
|
+
addInvalidRelationship("Relationship and upload fields must use a valid document ID.", itemPath);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
if (typeof item.value === "string" || typeof item.value === "number") {
|
|
44
|
+
const normalizedValue = normalizeRelationshipScalar(item.value);
|
|
45
|
+
if (normalizedValue === undefined) {
|
|
46
|
+
addInvalidRelationship("Relationship and upload fields must use a valid document ID.", itemPath);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (allowedRelationTargets.length > 1) {
|
|
50
|
+
const relationTo = typeof item.relationTo === "string" ? item.relationTo : null;
|
|
51
|
+
if (!relationTo || !allowedRelationTargets.includes(relationTo)) {
|
|
52
|
+
addInvalidRelationship(`Relationship must include one of: ${allowedRelationTargets.join(", ")}.`, itemPath);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
relationTo,
|
|
57
|
+
value: normalizedValue
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return normalizedValue;
|
|
61
|
+
}
|
|
62
|
+
addInvalidRelationship("Relationship and upload fields must use a document ID or an object with id/value.", itemPath);
|
|
63
|
+
return undefined;
|
|
64
|
+
};
|
|
65
|
+
if (!field.hasMany) return normalizeSingle(value, path);
|
|
66
|
+
if (!Array.isArray(value)) {
|
|
67
|
+
addInvalidRelationship("Relationship field expects an array of document IDs.", path);
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
return value.map((item, index)=>normalizeSingle(item, `${path}.${index}`)).filter((item)=>item !== undefined);
|
|
71
|
+
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
value: string;
|
|
4
|
-
};
|
|
1
|
+
import type { AIActionSignature } from "./types.js";
|
|
2
|
+
export type { AIActionSignature } from "./types.js";
|
|
5
3
|
type SignableProposal = Record<string, unknown> & {
|
|
6
4
|
_aiSignature?: AIActionSignature;
|
|
7
5
|
};
|
|
@@ -9,4 +7,3 @@ export declare const signAIActionProposal: <Proposal extends SignableProposal>(p
|
|
|
9
7
|
_aiSignature: AIActionSignature;
|
|
10
8
|
};
|
|
11
9
|
export declare const verifyActionProposal: (proposal: SignableProposal) => boolean;
|
|
12
|
-
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
-
import { isRecord } from "
|
|
2
|
+
import { isRecord } from "../../utils/data.js";
|
|
3
3
|
const signatureTTL = 10 * 60 * 1000;
|
|
4
4
|
const stableStringify = (value)=>{
|
|
5
5
|
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PayloadHandler } from "payload";
|
|
2
|
+
import type { CollectionConfig, FieldConfig } from "../schema/normalize.js";
|
|
3
|
+
import { type PreparedProposalData } from "./data.js";
|
|
4
|
+
import type { ActionProposal, LocalizedDataInput } from "./types.js";
|
|
5
|
+
type Request = Parameters<PayloadHandler>[0];
|
|
6
|
+
type WriteProposal = Exclude<ActionProposal, {
|
|
7
|
+
action: "delete";
|
|
8
|
+
}>;
|
|
9
|
+
type ReadOptions = {
|
|
10
|
+
fallbackLocale?: false;
|
|
11
|
+
locale?: string;
|
|
12
|
+
};
|
|
13
|
+
type BaseProposalTarget = {
|
|
14
|
+
fields: FieldConfig[];
|
|
15
|
+
read: (options?: ReadOptions) => Promise<Record<string, unknown>>;
|
|
16
|
+
schema: CollectionConfig;
|
|
17
|
+
update: (data: Record<string, unknown>, locale?: string, documentID?: string) => Promise<Record<string, unknown>>;
|
|
18
|
+
};
|
|
19
|
+
export type CollectionProposalTarget = BaseProposalTarget & {
|
|
20
|
+
collection: string;
|
|
21
|
+
create: (data: Record<string, unknown>, locale?: string) => Promise<Record<string, unknown>>;
|
|
22
|
+
delete: () => Promise<Record<string, unknown>>;
|
|
23
|
+
documentID?: string;
|
|
24
|
+
type: "collection";
|
|
25
|
+
};
|
|
26
|
+
export type GlobalProposalTarget = BaseProposalTarget & {
|
|
27
|
+
slug: string;
|
|
28
|
+
type: "global";
|
|
29
|
+
};
|
|
30
|
+
export type ProposalTarget = CollectionProposalTarget | GlobalProposalTarget;
|
|
31
|
+
export declare const resolveProposalTarget: ({ proposal, req }: {
|
|
32
|
+
proposal: ActionProposal;
|
|
33
|
+
req: Request;
|
|
34
|
+
}) => ProposalTarget | null;
|
|
35
|
+
export declare const prepareProposalTargetData: ({ inferenceText, proposal, target, }: {
|
|
36
|
+
inferenceText?: string;
|
|
37
|
+
proposal: WriteProposal;
|
|
38
|
+
target: ProposalTarget;
|
|
39
|
+
}) => PreparedProposalData;
|
|
40
|
+
export declare const prepareLocalizedTargetUpdates: ({ defaultLocale, localizedData, target, }: {
|
|
41
|
+
defaultLocale?: string;
|
|
42
|
+
localizedData: LocalizedDataInput;
|
|
43
|
+
target: ProposalTarget;
|
|
44
|
+
}) => Promise<{
|
|
45
|
+
after: Record<string, unknown>;
|
|
46
|
+
before: Record<string, unknown>;
|
|
47
|
+
data: {
|
|
48
|
+
[x: string]: unknown;
|
|
49
|
+
};
|
|
50
|
+
locale: string;
|
|
51
|
+
}[]>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { getSchemaFields } from "../schema/normalize.js";
|
|
2
|
+
import { applyLocalizedRequiredFallbackToPreparedData, prepareProposalWriteData } from "./data.js";
|
|
3
|
+
import { mergeData } from "../../utils/data.js";
|
|
4
|
+
const getLocaleOptions = ({ fallbackLocale, locale } = {})=>({
|
|
5
|
+
...fallbackLocale === false ? {
|
|
6
|
+
fallbackLocale: false
|
|
7
|
+
} : {},
|
|
8
|
+
...locale ? {
|
|
9
|
+
locale
|
|
10
|
+
} : {}
|
|
11
|
+
});
|
|
12
|
+
export const resolveProposalTarget = ({ proposal, req })=>{
|
|
13
|
+
if (proposal.action === "updateGlobal") {
|
|
14
|
+
const config = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
|
|
15
|
+
if (!config) return null;
|
|
16
|
+
const schema = {
|
|
17
|
+
fields: config.fields || [],
|
|
18
|
+
slug: proposal.slug
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
fields: getSchemaFields(schema),
|
|
22
|
+
read: async (options)=>await req.payload.findGlobal({
|
|
23
|
+
depth: 2,
|
|
24
|
+
...getLocaleOptions(options),
|
|
25
|
+
overrideAccess: false,
|
|
26
|
+
req,
|
|
27
|
+
slug: proposal.slug
|
|
28
|
+
}),
|
|
29
|
+
schema,
|
|
30
|
+
slug: proposal.slug,
|
|
31
|
+
type: "global",
|
|
32
|
+
update: async (data, locale)=>await req.payload.updateGlobal({
|
|
33
|
+
data,
|
|
34
|
+
...locale ? {
|
|
35
|
+
locale
|
|
36
|
+
} : {},
|
|
37
|
+
overrideAccess: false,
|
|
38
|
+
req,
|
|
39
|
+
slug: proposal.slug
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const config = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
|
|
44
|
+
if (!config) return null;
|
|
45
|
+
const documentID = proposal.action === "create" ? undefined : proposal.id;
|
|
46
|
+
const requireDocumentID = (overrideDocumentID)=>{
|
|
47
|
+
const resolvedDocumentID = overrideDocumentID || documentID;
|
|
48
|
+
if (!resolvedDocumentID) throw new Error(`Proposal target ${proposal.collection} requires a document id.`);
|
|
49
|
+
return resolvedDocumentID;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
collection: proposal.collection,
|
|
53
|
+
create: async (data, locale)=>await req.payload.create({
|
|
54
|
+
collection: proposal.collection,
|
|
55
|
+
data,
|
|
56
|
+
...locale ? {
|
|
57
|
+
locale
|
|
58
|
+
} : {},
|
|
59
|
+
overrideAccess: false,
|
|
60
|
+
req
|
|
61
|
+
}),
|
|
62
|
+
delete: async ()=>await req.payload.delete({
|
|
63
|
+
collection: proposal.collection,
|
|
64
|
+
id: requireDocumentID(),
|
|
65
|
+
overrideAccess: false,
|
|
66
|
+
req
|
|
67
|
+
}),
|
|
68
|
+
documentID,
|
|
69
|
+
fields: getSchemaFields(config),
|
|
70
|
+
read: async (options)=>await req.payload.findByID({
|
|
71
|
+
collection: proposal.collection,
|
|
72
|
+
depth: 2,
|
|
73
|
+
id: requireDocumentID(),
|
|
74
|
+
...getLocaleOptions(options),
|
|
75
|
+
overrideAccess: false,
|
|
76
|
+
req
|
|
77
|
+
}),
|
|
78
|
+
schema: config,
|
|
79
|
+
type: "collection",
|
|
80
|
+
update: async (data, locale, updateDocumentID)=>await req.payload.update({
|
|
81
|
+
collection: proposal.collection,
|
|
82
|
+
data,
|
|
83
|
+
id: requireDocumentID(updateDocumentID),
|
|
84
|
+
...locale ? {
|
|
85
|
+
locale
|
|
86
|
+
} : {},
|
|
87
|
+
overrideAccess: false,
|
|
88
|
+
req
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
export const prepareProposalTargetData = ({ inferenceText, proposal, target })=>prepareProposalWriteData({
|
|
93
|
+
collectionConfig: target.schema,
|
|
94
|
+
..."localizedData" in proposal ? {
|
|
95
|
+
localizedData: proposal.localizedData
|
|
96
|
+
} : {
|
|
97
|
+
data: proposal.data
|
|
98
|
+
},
|
|
99
|
+
inferenceText,
|
|
100
|
+
label: proposal.label,
|
|
101
|
+
mode: proposal.action === "create" ? "create" : "update"
|
|
102
|
+
});
|
|
103
|
+
export const prepareLocalizedTargetUpdates = async ({ defaultLocale, localizedData, target })=>{
|
|
104
|
+
const defaultLocaleDoc = defaultLocale ? await target.read({
|
|
105
|
+
fallbackLocale: false,
|
|
106
|
+
locale: defaultLocale
|
|
107
|
+
}) : null;
|
|
108
|
+
return Promise.all(Object.entries(localizedData).map(async ([locale, localeData])=>{
|
|
109
|
+
const before = locale === defaultLocale && defaultLocaleDoc ? defaultLocaleDoc : await target.read({
|
|
110
|
+
fallbackLocale: false,
|
|
111
|
+
locale
|
|
112
|
+
});
|
|
113
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
114
|
+
fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
|
|
115
|
+
fields: target.fields,
|
|
116
|
+
preparedData: localeData
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
after: mergeData(before, completedData),
|
|
120
|
+
before,
|
|
121
|
+
data: completedData,
|
|
122
|
+
locale
|
|
123
|
+
};
|
|
124
|
+
}));
|
|
125
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type AIActionSignature = {
|
|
2
|
+
expiresAt: string;
|
|
3
|
+
value: string;
|
|
4
|
+
};
|
|
5
|
+
export type LocalizedDataInput = Record<string, Record<string, unknown>>;
|
|
6
|
+
export type ProposalWritePayload = {
|
|
7
|
+
data: Record<string, unknown>;
|
|
8
|
+
localizedData?: never;
|
|
9
|
+
} | {
|
|
10
|
+
data?: never;
|
|
11
|
+
localizedData: LocalizedDataInput;
|
|
12
|
+
};
|
|
13
|
+
export type ActionProposal = (({
|
|
14
|
+
action: "create";
|
|
15
|
+
collection: string;
|
|
16
|
+
label: string;
|
|
17
|
+
} & ProposalWritePayload) | {
|
|
18
|
+
action: "delete";
|
|
19
|
+
collection: string;
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
} | ({
|
|
23
|
+
action: "update";
|
|
24
|
+
collection: string;
|
|
25
|
+
id: string;
|
|
26
|
+
label: string;
|
|
27
|
+
} & ProposalWritePayload) | ({
|
|
28
|
+
action: "updateGlobal";
|
|
29
|
+
label: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
} & ProposalWritePayload)) & {
|
|
32
|
+
_aiSignature?: AIActionSignature;
|
|
33
|
+
collection?: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
locale?: string;
|
|
36
|
+
slug?: string;
|
|
37
|
+
};
|
|
38
|
+
export type ActionProposalReference = {
|
|
39
|
+
action: ActionProposal["action"];
|
|
40
|
+
collection?: string;
|
|
41
|
+
id?: string;
|
|
42
|
+
label: string;
|
|
43
|
+
locale?: string;
|
|
44
|
+
slug?: string;
|
|
45
|
+
};
|
|
46
|
+
export type ProposalDiff = {
|
|
47
|
+
after: unknown;
|
|
48
|
+
before: unknown;
|
|
49
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ActionProposal } from "./types.js";
|
|
2
|
+
type ProposalValidationError = "invalid_shape" | "invalid_signature" | "missing";
|
|
3
|
+
export type SignedProposalValidation = {
|
|
4
|
+
error: ProposalValidationError;
|
|
5
|
+
ok: false;
|
|
6
|
+
proposal?: Partial<ActionProposal>;
|
|
7
|
+
} | {
|
|
8
|
+
ok: true;
|
|
9
|
+
proposal: ActionProposal;
|
|
10
|
+
};
|
|
11
|
+
export declare const validateSignedProposal: (value: unknown) => SignedProposalValidation;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isActionProposal, isRecord } from "../../utils/data.js";
|
|
2
|
+
import { verifyActionProposal } from "./signing.js";
|
|
3
|
+
export const validateSignedProposal = (value)=>{
|
|
4
|
+
if (value === undefined || value === null) return {
|
|
5
|
+
error: "missing",
|
|
6
|
+
ok: false
|
|
7
|
+
};
|
|
8
|
+
if (!isRecord(value)) return {
|
|
9
|
+
error: "invalid_shape",
|
|
10
|
+
ok: false
|
|
11
|
+
};
|
|
12
|
+
if (!verifyActionProposal(value)) return {
|
|
13
|
+
error: "invalid_signature",
|
|
14
|
+
ok: false,
|
|
15
|
+
proposal: value
|
|
16
|
+
};
|
|
17
|
+
if (!isActionProposal(value)) return {
|
|
18
|
+
error: "invalid_shape",
|
|
19
|
+
ok: false,
|
|
20
|
+
proposal: value
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
ok: true,
|
|
24
|
+
proposal: value
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { LanguageModel } from "ai";
|
|
2
|
+
import type { PayloadHandler } from "payload";
|
|
3
|
+
import { type AIModelConfig, type AIProvider, type ResolvedAIProviderConfig } from "./options.js";
|
|
4
|
+
import { type ResolvedMaxTokenUsageOptions, type TokenUsageData, type TokenUsageLimit } from "../tokenUsage.js";
|
|
5
|
+
export type AIRequestOptions = {
|
|
6
|
+
allowUserApiKeys?: boolean;
|
|
7
|
+
maxTokenUsage?: ResolvedMaxTokenUsageOptions;
|
|
8
|
+
models?: AIModelConfig;
|
|
9
|
+
providers?: ResolvedAIProviderConfig[];
|
|
10
|
+
};
|
|
11
|
+
export type AIRequestUser = {
|
|
12
|
+
aiApiKey?: string | null;
|
|
13
|
+
aiProvider?: AIProvider | string | null;
|
|
14
|
+
id: number | string;
|
|
15
|
+
};
|
|
16
|
+
type AIRequestFailure = {
|
|
17
|
+
code: "missing_api_key";
|
|
18
|
+
managedProvider: boolean;
|
|
19
|
+
message: string;
|
|
20
|
+
modelID: string;
|
|
21
|
+
provider: AIProvider;
|
|
22
|
+
providerID: string;
|
|
23
|
+
} | {
|
|
24
|
+
code: "token_limit";
|
|
25
|
+
message: string;
|
|
26
|
+
tokenLimit: TokenUsageLimit;
|
|
27
|
+
} | {
|
|
28
|
+
code: "unsupported_model" | "unsupported_provider";
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
export type AIRequestContext = {
|
|
32
|
+
loadModel: () => Promise<LanguageModel>;
|
|
33
|
+
managedProvider: ResolvedAIProviderConfig | null;
|
|
34
|
+
modelID: string;
|
|
35
|
+
provider: AIProvider;
|
|
36
|
+
providerID: string;
|
|
37
|
+
recordUsage: (usage: TokenUsageData) => Promise<void>;
|
|
38
|
+
};
|
|
39
|
+
export type AIRequestResolution = {
|
|
40
|
+
context: AIRequestContext;
|
|
41
|
+
ok: true;
|
|
42
|
+
} | {
|
|
43
|
+
error: AIRequestFailure;
|
|
44
|
+
ok: false;
|
|
45
|
+
};
|
|
46
|
+
export declare const resolveAIRequestContext: ({ options, req, requestedModel, requestedProvider, user, }: {
|
|
47
|
+
options: AIRequestOptions;
|
|
48
|
+
req: Parameters<PayloadHandler>[0];
|
|
49
|
+
requestedModel?: string | null;
|
|
50
|
+
requestedProvider?: string | null;
|
|
51
|
+
user: AIRequestUser;
|
|
52
|
+
}) => Promise<AIRequestResolution>;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isAIProvider } from "./options.js";
|
|
2
|
+
import { getModel, getProviderConfig } from "./runtime.js";
|
|
3
|
+
import { getExceededTokenUsageLimit, recordTokenUsage } from "../tokenUsage.js";
|
|
4
|
+
export const resolveAIRequestContext = async ({ options, req, requestedModel, requestedProvider, user })=>{
|
|
5
|
+
const tokenLimit = await getExceededTokenUsageLimit({
|
|
6
|
+
maxTokenUsage: options.maxTokenUsage,
|
|
7
|
+
req,
|
|
8
|
+
userID: user.id
|
|
9
|
+
});
|
|
10
|
+
if (tokenLimit) {
|
|
11
|
+
return {
|
|
12
|
+
error: {
|
|
13
|
+
code: "token_limit",
|
|
14
|
+
message: "AI token usage limit reached.",
|
|
15
|
+
tokenLimit
|
|
16
|
+
},
|
|
17
|
+
ok: false
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const managedProviders = options.providers?.length ? options.providers : null;
|
|
21
|
+
const providerSelection = requestedProvider || (managedProviders ? managedProviders[0].id : user.aiProvider || "openai");
|
|
22
|
+
const managedProvider = managedProviders?.find((providerConfig)=>providerConfig.id === providerSelection) || null;
|
|
23
|
+
if (managedProviders && !managedProvider || !managedProvider && !isAIProvider(providerSelection)) {
|
|
24
|
+
return {
|
|
25
|
+
error: {
|
|
26
|
+
code: "unsupported_provider",
|
|
27
|
+
message: `Unsupported AI provider: ${providerSelection}`
|
|
28
|
+
},
|
|
29
|
+
ok: false
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const provider = managedProvider?.provider || providerSelection;
|
|
33
|
+
const modelSelection = requestedModel || managedProvider?.defaultModel;
|
|
34
|
+
if (managedProvider && modelSelection && !managedProvider.models.some((model)=>model.value === modelSelection)) {
|
|
35
|
+
return {
|
|
36
|
+
error: {
|
|
37
|
+
code: "unsupported_model",
|
|
38
|
+
message: `Unsupported model "${modelSelection}" for AI provider "${managedProvider.id}".`
|
|
39
|
+
},
|
|
40
|
+
ok: false
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const providerConfig = getProviderConfig({
|
|
44
|
+
apiKey: managedProvider ? managedProvider.apiKey : options.allowUserApiKeys === false ? null : user.aiApiKey,
|
|
45
|
+
defaultModels: options.models?.defaults,
|
|
46
|
+
model: modelSelection,
|
|
47
|
+
provider
|
|
48
|
+
});
|
|
49
|
+
const providerID = managedProvider?.id || provider;
|
|
50
|
+
if (!providerConfig.apiKey) {
|
|
51
|
+
return {
|
|
52
|
+
error: {
|
|
53
|
+
code: "missing_api_key",
|
|
54
|
+
managedProvider: Boolean(managedProvider),
|
|
55
|
+
message: "Configure an AI provider API key first.",
|
|
56
|
+
modelID: providerConfig.modelID,
|
|
57
|
+
provider,
|
|
58
|
+
providerID
|
|
59
|
+
},
|
|
60
|
+
ok: false
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
context: {
|
|
65
|
+
loadModel: ()=>getModel({
|
|
66
|
+
apiKey: providerConfig.apiKey,
|
|
67
|
+
...managedProvider?.baseURL ? {
|
|
68
|
+
baseURL: managedProvider.baseURL
|
|
69
|
+
} : {},
|
|
70
|
+
model: providerConfig.modelID,
|
|
71
|
+
provider
|
|
72
|
+
}),
|
|
73
|
+
managedProvider,
|
|
74
|
+
modelID: providerConfig.modelID,
|
|
75
|
+
provider,
|
|
76
|
+
providerID,
|
|
77
|
+
recordUsage: async (usage)=>{
|
|
78
|
+
if (!options.maxTokenUsage) return;
|
|
79
|
+
await recordTokenUsage({
|
|
80
|
+
model: providerConfig.modelID,
|
|
81
|
+
provider: providerID,
|
|
82
|
+
req,
|
|
83
|
+
usage,
|
|
84
|
+
userID: user.id
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
ok: true
|
|
89
|
+
};
|
|
90
|
+
};
|