@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,128 +1,63 @@
1
- import { verifyActionProposal } from "../ai/proposalSigning.js";
2
- import { redactSensitiveData } from "../ai/sensitiveData.js";
3
- import { getSchemaFields } from "../payload/normalizeData.js";
4
- import { applyLocalizedRequiredFallbackToPreparedData, prepareProposalWriteData } from "../payload/proposalData.js";
5
- import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
6
- import { getDefaultLocale, hasLocalizedData, isActionProposal, mergeData } from "../payload/shared.js";
7
- export const createProposalDiffHandler = (options = {})=>async (req)=>{
8
- if (!req.user) return Response.json({
9
- error: "Unauthorized"
10
- }, {
11
- status: 401
12
- });
13
- const body = req.json ? await req.json().catch(()=>null) : null;
14
- const proposal = body?.proposal;
15
- if (!proposal) return Response.json({
16
- error: "Proposal is required"
17
- }, {
18
- status: 400
19
- });
20
- if (!verifyActionProposal(proposal)) return Response.json({
21
- error: "Proposal signature is invalid or expired."
22
- }, {
23
- status: 400
24
- });
25
- if (!isActionProposal(proposal)) return Response.json({
26
- error: "Proposal is invalid."
27
- }, {
28
- status: 400
29
- });
1
+ import { redactSensitiveData } from "../features/sensitiveData.js";
2
+ import { validateSignedProposal } from "../features/proposals/validation.js";
3
+ import { prepareLocalizedTargetUpdates, prepareProposalTargetData, resolveProposalTarget } from "../features/proposals/target.js";
4
+ import { applyLocalizedRequiredFallbackToPreparedData } from "../features/proposals/data.js";
5
+ import { isCollectionActionAllowed } from "../features/collectionPermissions.js";
6
+ import { getDefaultLocale, hasLocalizedData, mergeData } from "../utils/data.js";
7
+ import { jsonError, logHandlerError, readJSONBody, withAuthenticatedHandler } from "./http.js";
8
+ export const createProposalDiffHandler = (options = {})=>withAuthenticatedHandler(async (req)=>{
9
+ const body = await readJSONBody(req);
10
+ const proposalValidation = validateSignedProposal(body?.proposal);
11
+ if (!proposalValidation.ok) {
12
+ if (proposalValidation.error === "missing") return jsonError("Proposal is required");
13
+ if (proposalValidation.error === "invalid_signature") return jsonError("Proposal signature is invalid or expired.");
14
+ return jsonError("Proposal is invalid.");
15
+ }
16
+ const proposal = proposalValidation.proposal;
30
17
  try {
31
18
  const inferenceText = body?.prompt;
32
19
  const defaultLocale = getDefaultLocale(req);
20
+ const target = resolveProposalTarget({
21
+ proposal,
22
+ req
23
+ });
33
24
  if (proposal.action === "updateGlobal") {
34
- const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
35
- if (!globalConfig) return Response.json({
36
- error: "Unknown global"
37
- }, {
38
- status: 400
39
- });
25
+ if (!target || target.type !== "global") return jsonError("Unknown global");
40
26
  if (hasLocalizedData(proposal)) {
41
- const beforeByLocale = {};
42
- const afterByLocale = {};
43
- const globalFields = getSchemaFields({
44
- fields: globalConfig.fields || [],
45
- slug: proposal.slug
46
- });
47
- const preparedData = prepareProposalWriteData({
48
- collectionConfig: {
49
- fields: globalConfig.fields || [],
50
- slug: proposal.slug
51
- },
27
+ const preparedData = prepareProposalTargetData({
52
28
  inferenceText,
53
- label: proposal.label,
54
- localizedData: proposal.localizedData,
55
- mode: "update"
29
+ proposal,
30
+ target
56
31
  });
57
32
  if (preparedData.issues.length > 0 || !preparedData.localizedData) {
58
- return Response.json({
59
- error: "Proposal is invalid."
60
- }, {
61
- status: 400
62
- });
33
+ return jsonError("Proposal is invalid.");
63
34
  }
64
- const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
65
- depth: 2,
66
- fallbackLocale: false,
67
- locale: defaultLocale,
68
- overrideAccess: false,
69
- req,
70
- slug: proposal.slug
71
- }) : null;
72
- const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
73
- const doc = await req.payload.findGlobal({
74
- depth: 2,
75
- fallbackLocale: false,
76
- locale,
77
- overrideAccess: false,
78
- req,
79
- slug: proposal.slug
80
- });
81
- const completedData = applyLocalizedRequiredFallbackToPreparedData({
82
- fallbackSource: locale === defaultLocale ? doc : defaultLocaleDoc || doc,
83
- fields: globalFields,
84
- preparedData: localeData
85
- });
86
- return {
87
- completedData,
88
- doc,
89
- locale
90
- };
91
- }));
92
- localizedResults.forEach(({ completedData, doc, locale })=>{
93
- beforeByLocale[locale] = redactSensitiveData(doc);
94
- afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
35
+ const localizedResults = await prepareLocalizedTargetUpdates({
36
+ defaultLocale: defaultLocale || undefined,
37
+ localizedData: preparedData.localizedData,
38
+ target
95
39
  });
96
40
  return Response.json({
97
- after: afterByLocale,
98
- before: beforeByLocale
41
+ after: Object.fromEntries(localizedResults.map(({ after, locale })=>[
42
+ locale,
43
+ redactSensitiveData(after)
44
+ ])),
45
+ before: Object.fromEntries(localizedResults.map(({ before, locale })=>[
46
+ locale,
47
+ redactSensitiveData(before)
48
+ ]))
99
49
  });
100
50
  }
101
- const preparedData = prepareProposalWriteData({
102
- collectionConfig: {
103
- fields: globalConfig.fields || [],
104
- slug: proposal.slug
105
- },
106
- data: proposal.data,
51
+ const preparedData = prepareProposalTargetData({
107
52
  inferenceText,
108
- label: proposal.label,
109
- mode: "update"
53
+ proposal,
54
+ target
110
55
  });
111
56
  if (preparedData.issues.length > 0 || !preparedData.data) {
112
- return Response.json({
113
- error: "Proposal is invalid."
114
- }, {
115
- status: 400
116
- });
57
+ return jsonError("Proposal is invalid.");
117
58
  }
118
- const doc = await req.payload.findGlobal({
119
- depth: 2,
120
- ...proposal.locale ? {
121
- locale: proposal.locale
122
- } : {},
123
- overrideAccess: false,
124
- req,
125
- slug: proposal.slug
59
+ const doc = await target.read({
60
+ locale: proposal.locale
126
61
  });
127
62
  return Response.json({
128
63
  after: redactSensitiveData(mergeData(doc, preparedData.data)),
@@ -134,61 +69,34 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
134
69
  permissions: options.collections,
135
70
  req,
136
71
  slug: proposal.collection
137
- })) return Response.json({
138
- error: "Unknown collection"
139
- }, {
140
- status: 400
141
- });
72
+ })) return jsonError("Unknown collection");
73
+ if (!target || target.type !== "collection") return jsonError("Unknown collection");
142
74
  if (proposal.action === "delete") {
143
- const doc = await req.payload.findByID({
144
- collection: proposal.collection,
145
- depth: 2,
146
- id: proposal.id,
147
- ...proposal.locale ? {
148
- locale: proposal.locale
149
- } : {},
150
- overrideAccess: false,
151
- req
75
+ const doc = await target.read({
76
+ locale: proposal.locale
152
77
  });
153
78
  return Response.json({
154
79
  after: {},
155
80
  before: redactSensitiveData(doc)
156
81
  });
157
82
  }
158
- const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
159
- const collectionFields = getSchemaFields(collectionConfig);
160
83
  if (hasLocalizedData(proposal)) {
161
- const preparedData = prepareProposalWriteData({
162
- collectionConfig,
84
+ const preparedData = prepareProposalTargetData({
163
85
  inferenceText,
164
- label: proposal.label,
165
- localizedData: proposal.localizedData,
166
- mode: proposal.action
86
+ proposal,
87
+ target
167
88
  });
168
89
  if (preparedData.issues.length > 0 || !preparedData.localizedData) {
169
- return Response.json({
170
- error: "Proposal is invalid."
171
- }, {
172
- status: 400
173
- });
90
+ return jsonError("Proposal is invalid.");
174
91
  }
175
92
  const afterByLocale = {};
176
93
  const beforeByLocale = {};
177
- const defaultLocaleDoc = proposal.action === "update" && defaultLocale ? await req.payload.findByID({
178
- collection: proposal.collection,
179
- depth: 2,
180
- fallbackLocale: false,
181
- id: proposal.id,
182
- locale: defaultLocale,
183
- overrideAccess: false,
184
- req
185
- }) : null;
186
94
  let createFallbackSource = null;
187
95
  if (proposal.action === "create") {
188
96
  for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
189
97
  const completedData = applyLocalizedRequiredFallbackToPreparedData({
190
98
  fallbackSource: createFallbackSource || {},
191
- fields: collectionFields,
99
+ fields: target.fields,
192
100
  preparedData: localeData
193
101
  });
194
102
  beforeByLocale[locale] = {};
@@ -196,30 +104,14 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
196
104
  createFallbackSource = mergeData(createFallbackSource || {}, completedData);
197
105
  }
198
106
  } else {
199
- const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
200
- const doc = await req.payload.findByID({
201
- collection: proposal.collection,
202
- depth: 2,
203
- fallbackLocale: false,
204
- id: proposal.id,
205
- locale,
206
- overrideAccess: false,
207
- req
208
- });
209
- const completedData = applyLocalizedRequiredFallbackToPreparedData({
210
- fallbackSource: locale === defaultLocale ? doc : defaultLocaleDoc || {},
211
- fields: collectionFields,
212
- preparedData: localeData
213
- });
214
- return {
215
- completedData,
216
- doc,
217
- locale
218
- };
219
- }));
220
- localizedResults.forEach(({ completedData, doc, locale })=>{
221
- beforeByLocale[locale] = redactSensitiveData(doc);
222
- afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
107
+ const localizedResults = await prepareLocalizedTargetUpdates({
108
+ defaultLocale: defaultLocale || undefined,
109
+ localizedData: preparedData.localizedData,
110
+ target
111
+ });
112
+ localizedResults.forEach(({ after, before, locale })=>{
113
+ beforeByLocale[locale] = redactSensitiveData(before);
114
+ afterByLocale[locale] = redactSensitiveData(after);
223
115
  });
224
116
  }
225
117
  return Response.json({
@@ -227,19 +119,13 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
227
119
  before: beforeByLocale
228
120
  });
229
121
  }
230
- const preparedData = prepareProposalWriteData({
231
- collectionConfig,
232
- data: proposal.data,
122
+ const preparedData = prepareProposalTargetData({
233
123
  inferenceText,
234
- label: proposal.label,
235
- mode: proposal.action
124
+ proposal,
125
+ target
236
126
  });
237
127
  if (preparedData.issues.length > 0 || !preparedData.data) {
238
- return Response.json({
239
- error: "Proposal is invalid."
240
- }, {
241
- status: 400
242
- });
128
+ return jsonError("Proposal is invalid.");
243
129
  }
244
130
  if (proposal.action === "create") {
245
131
  return Response.json({
@@ -247,29 +133,20 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
247
133
  before: {}
248
134
  });
249
135
  }
250
- const doc = await req.payload.findByID({
251
- collection: proposal.collection,
252
- depth: 2,
253
- id: proposal.id,
254
- ...proposal.locale ? {
255
- locale: proposal.locale
256
- } : {},
257
- overrideAccess: false,
258
- req
136
+ const doc = await target.read({
137
+ locale: proposal.locale
259
138
  });
260
139
  return Response.json({
261
140
  after: redactSensitiveData(mergeData(doc, preparedData.data)),
262
141
  before: redactSensitiveData(doc)
263
142
  });
264
143
  } catch (err) {
265
- req.payload.logger.error({
266
- err,
267
- msg: "AI proposal diff failed"
268
- });
269
- return Response.json({
270
- error: "Could not load proposal diff."
271
- }, {
144
+ return logHandlerError({
145
+ error: err,
146
+ logMessage: "AI proposal diff failed",
147
+ publicMessage: "Could not load proposal diff.",
148
+ req,
272
149
  status: 400
273
150
  });
274
151
  }
275
- };
152
+ });
@@ -1,14 +1,9 @@
1
1
  import type { PayloadHandler } from "payload";
2
- import { type AIModelConfig, type ResolvedAIProviderConfig } from "../ai/providerOptions.js";
3
- import { type ResolvedMaxTokenUsageOptions } from "../ai/tokenUsage.js";
4
- import { type TranslationPageContext } from "../payload/documentTranslation.js";
5
- type TranslateDocumentOptions = {
6
- allowUserApiKeys?: boolean;
2
+ import { type AIRequestOptions } from "../features/providers/requestContext.js";
3
+ import { type TranslationPageContext } from "../features/content/documentTranslation.js";
4
+ type TranslateDocumentOptions = AIRequestOptions & {
7
5
  maxOutputTokens?: number;
8
- maxTokenUsage?: ResolvedMaxTokenUsageOptions;
9
- models?: AIModelConfig;
10
6
  pageContexts: Map<string, TranslationPageContext>;
11
- providers?: ResolvedAIProviderConfig[];
12
7
  };
13
8
  export declare const createTranslateDocumentHandler: (options: TranslateDocumentOptions) => PayloadHandler;
14
9
  export {};
@@ -1,9 +1,8 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
2
  import { generateText } from "ai";
3
- import { isAIProvider } from "../ai/providerOptions.js";
4
- import { getModel, getProviderConfig } from "../ai/providerRuntime.js";
5
- import { getExceededTokenUsageLimit, recordTokenUsage } from "../ai/tokenUsage.js";
6
- import { getTranslationValues } from "../payload/documentTranslation.js";
3
+ import { resolveAIRequestContext } from "../features/providers/requestContext.js";
4
+ import { getTranslationValues } from "../features/content/documentTranslation.js";
5
+ import { jsonError, logHandlerError, readJSONBody, withAuthenticatedHandler } from "./http.js";
7
6
  const getDefaultLocale = (req)=>{
8
7
  const localization = req.payload.config.localization;
9
8
  if (!localization) return null;
@@ -39,22 +38,13 @@ const parseTranslations = (text, sourceCount)=>{
39
38
  length: sourceCount
40
39
  }, (_, index)=>parsed.translations?.[String(index)]);
41
40
  };
42
- export const createTranslateDocumentHandler = (options)=>async (req)=>{
43
- if (!req.user) return Response.json({
44
- error: "Unauthorized"
45
- }, {
46
- status: 401
47
- });
48
- const body = req.json ? await req.json().catch(()=>null) : null;
41
+ export const createTranslateDocumentHandler = (options)=>withAuthenticatedHandler(async (req)=>{
42
+ const body = await readJSONBody(req);
49
43
  const locale = body?.locale?.trim();
50
44
  const scopeSlug = body?.scope?.slug?.trim();
51
45
  const scopeType = body?.scope?.type;
52
46
  if (!body?.action || !locale || !scopeSlug || !scopeType) {
53
- return Response.json({
54
- error: "Action, locale, and page scope are required."
55
- }, {
56
- status: 400
57
- });
47
+ return jsonError("Action, locale, and page scope are required.");
58
48
  }
59
49
  const pageContext = options.pageContexts.get(`${scopeType}:${scopeSlug}`);
60
50
  const defaultLocale = getDefaultLocale(req);
@@ -93,55 +83,18 @@ export const createTranslateDocumentHandler = (options)=>async (req)=>{
93
83
  if (body.action === "status" || !available) return Response.json({
94
84
  available
95
85
  });
96
- const user = req.user;
97
- const exceededLimit = await getExceededTokenUsageLimit({
98
- maxTokenUsage: options.maxTokenUsage,
86
+ const aiRequestResolution = await resolveAIRequestContext({
87
+ options,
99
88
  req,
100
- userID: user.id
101
- });
102
- if (exceededLimit) return Response.json({
103
- error: "AI token usage limit reached."
104
- }, {
105
- status: 429
89
+ requestedModel: body.model,
90
+ requestedProvider: body.provider,
91
+ user: req.user
106
92
  });
107
- const managedProviders = options.providers?.length ? options.providers : null;
108
- const requestedProvider = body.provider || (managedProviders ? managedProviders[0].id : user.aiProvider || "openai");
109
- const managedProvider = managedProviders?.find((provider)=>provider.id === requestedProvider);
110
- if (managedProviders && !managedProvider) {
111
- return Response.json({
112
- error: `Unsupported AI provider: ${requestedProvider}`
113
- }, {
114
- status: 400
115
- });
93
+ if (!aiRequestResolution.ok) {
94
+ return jsonError(aiRequestResolution.error.message, aiRequestResolution.error.code === "token_limit" ? 429 : 400);
116
95
  }
117
- if (!managedProvider && !isAIProvider(requestedProvider)) {
118
- return Response.json({
119
- error: `Unsupported AI provider: ${requestedProvider}`
120
- }, {
121
- status: 400
122
- });
123
- }
124
- const provider = managedProvider?.provider || requestedProvider;
125
- const requestedModel = body.model || managedProvider?.defaultModel;
126
- const providerConfig = getProviderConfig({
127
- apiKey: managedProvider ? managedProvider.apiKey : options.allowUserApiKeys === false ? null : user.aiApiKey,
128
- defaultModels: options.models?.defaults,
129
- model: requestedModel,
130
- provider
131
- });
132
- if (!providerConfig.apiKey) return Response.json({
133
- error: "Configure an AI provider API key first."
134
- }, {
135
- status: 400
136
- });
137
- const model = await getModel({
138
- apiKey: providerConfig.apiKey,
139
- ...managedProvider?.baseURL ? {
140
- baseURL: managedProvider.baseURL
141
- } : {},
142
- model: providerConfig.modelID,
143
- provider
144
- });
96
+ const aiRequest = aiRequestResolution.context;
97
+ const model = await aiRequest.loadModel();
145
98
  const result = await generateText({
146
99
  maxOutputTokens: options.maxOutputTokens || 700,
147
100
  model,
@@ -162,15 +115,7 @@ export const createTranslateDocumentHandler = (options)=>async (req)=>{
162
115
  });
163
116
  const translatedValues = parseTranslations(result.text, translationSourceValues.length);
164
117
  if (translatedValues.some((value)=>value === undefined)) throw new Error("Translation response is incomplete.");
165
- if (result.usage && options.maxTokenUsage) {
166
- await recordTokenUsage({
167
- model: providerConfig.modelID,
168
- provider: managedProvider?.id || provider,
169
- req,
170
- usage: result.usage,
171
- userID: user.id
172
- });
173
- }
118
+ if (result.usage) await aiRequest.recordUsage(result.usage);
174
119
  return Response.json({
175
120
  available: true,
176
121
  values: translationSourceValues.map((entry, index)=>({
@@ -180,16 +125,15 @@ export const createTranslateDocumentHandler = (options)=>async (req)=>{
180
125
  }))
181
126
  });
182
127
  } catch (error) {
183
- req.payload.logger.error({
184
- err: error,
185
- msg: "AI document translation failed",
186
- scopeSlug,
187
- scopeType
188
- });
189
- return Response.json({
190
- error: "AI document translation failed."
191
- }, {
192
- status: 500
128
+ return logHandlerError({
129
+ details: {
130
+ scopeSlug,
131
+ scopeType
132
+ },
133
+ error,
134
+ logMessage: "AI document translation failed",
135
+ publicMessage: "AI document translation failed.",
136
+ req
193
137
  });
194
138
  }
195
- };
139
+ });
package/dist/index.d.ts CHANGED
@@ -1,32 +1,6 @@
1
1
  import type { Config } from "payload";
2
- import { type AIModelConfig, type AIProviderConfig } from "./ai/providerOptions.js";
3
- import { type MaxTokenUsageOptions } from "./ai/tokenUsage.js";
4
- import { type CollectionPermissionMap } from "./payload/collectionPermissions.js";
5
- export type { AIModelConfig, AIProviderConfig, AIProviderModelOption } from "./ai/providerOptions.js";
6
- export type { MaxTokenUsageOptions } from "./ai/tokenUsage.js";
7
- export type CollectionTypeAIOptions = {
8
- aiInput?: boolean;
9
- generateFields?: boolean;
10
- };
11
- export type PayloadAIPluginOptions = {
12
- aiInput?: boolean;
13
- allowUserApiKeys?: boolean;
14
- authCollections?: CollectionTypeAIOptions;
15
- collections?: CollectionPermissionMap;
16
- disabled?: boolean;
17
- generateFields?: boolean;
18
- maxOutputTokens?: number;
19
- media?: {
20
- acceptedMimeTypes?: string[];
21
- collectionSlug?: string;
22
- enabled?: boolean;
23
- maxFileSize?: number;
24
- };
25
- models?: AIModelConfig;
26
- maxTokenUsage?: MaxTokenUsageOptions;
27
- promptCaching?: boolean;
28
- providers?: AIProviderConfig[];
29
- translate?: boolean;
30
- uploadCollections?: CollectionTypeAIOptions;
31
- };
2
+ import type { PayloadAIPluginOptions } from "./plugin/types.js";
3
+ export type { AIModelConfig, AIProviderConfig, AIProviderModelOption } from "./features/providers/options.js";
4
+ export type { MaxTokenUsageOptions } from "./features/tokenUsage.js";
5
+ export type { CollectionTypeAIOptions, PayloadAIPluginOptions } from "./plugin/types.js";
32
6
  export declare const payloadAiPlugin: (pluginOptions: PayloadAIPluginOptions) => (config: Config) => Config;